gitlab-radiator 4.4.5 → 5.1.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/README.md +11 -6
- package/package.json +27 -19
- package/public/client.js +1 -1
- package/public/client.less +5 -0
- package/public/colors.less +2 -0
- package/public/index.html +0 -1
- package/src/app.js +63 -74
- package/src/auth.js +15 -18
- package/src/config.js +78 -42
- package/src/dev-assets.js +11 -0
- package/src/gitlab/client.js +17 -24
- package/src/gitlab/index.js +41 -47
- package/src/gitlab/pipelines.js +114 -108
- package/src/gitlab/projects.js +53 -61
- package/src/gitlab/runners.js +19 -14
- package/src/less.js +25 -23
- package/src/tsconfig.build.tsbuildinfo +1 -0
package/src/gitlab/pipelines.js
CHANGED
|
@@ -1,119 +1,125 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
return pipelinesWithStages
|
|
1
|
+
import { gitlabRequest } from "./client.js";
|
|
2
|
+
export async function fetchLatestPipelines(projectId, gitlab, prioritizeRunningPipelines) {
|
|
3
|
+
const pipelines = await fetchLatestAndMasterPipeline(projectId, gitlab, prioritizeRunningPipelines);
|
|
4
|
+
const pipelinesWithStages = [];
|
|
5
|
+
for (const { id, ref, status } of pipelines) {
|
|
6
|
+
const { commit, stages } = await fetchJobs(projectId, id, gitlab);
|
|
7
|
+
const downstreamStages = await fetchDownstreamJobs(projectId, id, gitlab);
|
|
8
|
+
pipelinesWithStages.push({
|
|
9
|
+
id,
|
|
10
|
+
ref,
|
|
11
|
+
status,
|
|
12
|
+
commit,
|
|
13
|
+
stages: stages.concat(downstreamStages)
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
return pipelinesWithStages;
|
|
20
17
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
18
|
+
async function fetchLatestAndMasterPipeline(projectId, gitlab, prioritizeRunningPipelines) {
|
|
19
|
+
const options = {
|
|
20
|
+
per_page: 100,
|
|
21
|
+
...(gitlab.branch ? { ref: gitlab.branch } : {})
|
|
22
|
+
};
|
|
23
|
+
const pipelines = await fetchPipelines(projectId, gitlab, options);
|
|
24
|
+
if (pipelines.length === 0) {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
const runningPipelines = pipelines.filter(pipeline => pipeline.status === 'running');
|
|
28
|
+
if (runningPipelines.length > 1 && prioritizeRunningPipelines) {
|
|
29
|
+
return runningPipelines;
|
|
30
|
+
}
|
|
31
|
+
const latestPipeline = pipelines.slice(0, 1);
|
|
32
|
+
if (latestPipeline[0].ref === 'master') {
|
|
33
|
+
return latestPipeline;
|
|
34
|
+
}
|
|
35
|
+
const latestMasterPipeline = pipelines.filter(p => p.ref === 'master').slice(0, 1);
|
|
36
|
+
if (latestMasterPipeline.length > 0) {
|
|
37
|
+
return latestPipeline.concat(latestMasterPipeline);
|
|
38
|
+
}
|
|
39
|
+
const masterPipelines = await fetchPipelines(projectId, gitlab, { per_page: 50, ref: 'master' });
|
|
40
|
+
return latestPipeline.concat(masterPipelines.slice(0, 1));
|
|
38
41
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return pipelines.filter(pipeline => pipeline.status !== 'skipped')
|
|
42
|
+
async function fetchPipelines(projectId, gitlab, params) {
|
|
43
|
+
const { data: pipelines } = await gitlabRequest(`/projects/${projectId}/pipelines`, params, gitlab);
|
|
44
|
+
return pipelines.filter(pipeline => pipeline.status !== 'skipped');
|
|
43
45
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
57
|
-
return downstreamStages.flat()
|
|
46
|
+
async function fetchDownstreamJobs(projectId, pipelineId, gitlab) {
|
|
47
|
+
const { data: gitlabBridgeJobs } = await gitlabRequest(`/projects/${projectId}/pipelines/${pipelineId}/bridges`, { per_page: 100 }, gitlab);
|
|
48
|
+
const childPipelines = gitlabBridgeJobs.filter((bridge) => bridge.downstream_pipeline !== null && bridge.downstream_pipeline.status !== 'skipped');
|
|
49
|
+
const downstreamStages = [];
|
|
50
|
+
for (const childPipeline of childPipelines) {
|
|
51
|
+
const { stages } = await fetchJobs(childPipeline.downstream_pipeline.project_id, childPipeline.downstream_pipeline.id, gitlab);
|
|
52
|
+
downstreamStages.push(stages.map((stage) => ({
|
|
53
|
+
...stage,
|
|
54
|
+
name: `${childPipeline.stage}:${stage.name}`
|
|
55
|
+
})));
|
|
56
|
+
}
|
|
57
|
+
return downstreamStages.flat();
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
url: job.web_url
|
|
59
|
+
async function fetchJobs(projectId, pipelineId, gitlab) {
|
|
60
|
+
const { data: gitlabJobs } = await gitlabRequest(`/projects/${projectId}/pipelines/${pipelineId}/jobs?include_retried=true`, { per_page: 100 }, gitlab);
|
|
61
|
+
if (gitlabJobs.length === 0) {
|
|
62
|
+
return { commit: null, stages: [] };
|
|
63
|
+
}
|
|
64
|
+
const commit = findCommit(gitlabJobs);
|
|
65
|
+
// Map jobs and sort by id
|
|
66
|
+
const mappedJobs = gitlabJobs
|
|
67
|
+
.map(job => ({
|
|
68
|
+
id: job.id,
|
|
69
|
+
status: job.status,
|
|
70
|
+
stage: job.stage,
|
|
71
|
+
name: job.name,
|
|
72
|
+
startedAt: job.started_at,
|
|
73
|
+
finishedAt: job.finished_at,
|
|
74
|
+
url: job.web_url
|
|
76
75
|
}))
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
76
|
+
.sort((a, b) => a.id - b.id);
|
|
77
|
+
// Group by stage
|
|
78
|
+
const jobsByStage = new Map();
|
|
79
|
+
for (const job of mappedJobs) {
|
|
80
|
+
const stageJobs = jobsByStage.get(job.stage) || [];
|
|
81
|
+
stageJobs.push(job);
|
|
82
|
+
jobsByStage.set(job.stage, stageJobs);
|
|
83
|
+
}
|
|
84
|
+
// Convert to stages array
|
|
85
|
+
const stages = Array.from(jobsByStage.entries()).map(([name, jobs]) => ({
|
|
86
|
+
name,
|
|
87
|
+
jobs: mergeRetriedJobs(removeStageProperty(jobs)).sort(byName)
|
|
88
|
+
}));
|
|
89
|
+
return {
|
|
90
|
+
commit,
|
|
91
|
+
stages
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function byName(a, b) {
|
|
95
|
+
return a.name.localeCompare(b.name);
|
|
89
96
|
}
|
|
90
|
-
|
|
91
97
|
function findCommit(jobs) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
const [job] = jobs.filter(j => j.commit);
|
|
99
|
+
if (!job || !job.commit) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
title: job.commit.title,
|
|
104
|
+
author: job.commit.author_name
|
|
105
|
+
};
|
|
100
106
|
}
|
|
101
|
-
|
|
102
107
|
function mergeRetriedJobs(jobs) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
108
|
+
return jobs.reduce((mergedJobs, job) => {
|
|
109
|
+
const index = mergedJobs.findIndex(mergedJob => mergedJob.name === job.name);
|
|
110
|
+
if (index >= 0) {
|
|
111
|
+
mergedJobs[index] = job;
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
mergedJobs.push(job);
|
|
115
|
+
}
|
|
116
|
+
return mergedJobs;
|
|
117
|
+
}, []);
|
|
112
118
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
+
function removeStageProperty(jobs) {
|
|
120
|
+
return jobs.map(job => {
|
|
121
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
122
|
+
const { stage, ...rest } = job;
|
|
123
|
+
return rest;
|
|
124
|
+
});
|
|
119
125
|
}
|
package/src/gitlab/projects.js
CHANGED
|
@@ -1,73 +1,65 @@
|
|
|
1
|
-
import {gitlabRequest} from
|
|
2
|
-
|
|
1
|
+
import { gitlabRequest } from "./client.js";
|
|
3
2
|
export async function fetchProjects(gitlab) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
3
|
+
const projects = await fetchOwnProjects(gitlab);
|
|
4
|
+
return projects
|
|
5
|
+
// Ignore projects for which CI/CD is not enabled
|
|
6
|
+
.filter(project => project.jobs_enabled)
|
|
7
|
+
.map(projectMapper)
|
|
8
|
+
.filter(includeRegexFilter(gitlab))
|
|
9
|
+
.filter(excludeRegexFilter(gitlab))
|
|
10
|
+
.filter(archivedFilter(gitlab));
|
|
12
11
|
}
|
|
13
|
-
|
|
14
12
|
async function fetchOwnProjects(gitlab) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
const projects = [];
|
|
14
|
+
const SAFETY_MAX_PAGE = 10;
|
|
15
|
+
for (let page = 1; page <= SAFETY_MAX_PAGE; page += 1) {
|
|
16
|
+
const { data, headers } = await gitlabRequest('/projects', { page, per_page: 100, membership: true }, gitlab);
|
|
17
|
+
projects.push(...data);
|
|
18
|
+
if (data.length === 0 || !headers['x-next-page']) {
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
23
21
|
}
|
|
24
|
-
|
|
25
|
-
return projects.flat()
|
|
22
|
+
return projects;
|
|
26
23
|
}
|
|
27
|
-
|
|
28
24
|
function projectMapper(project) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
25
|
+
return {
|
|
26
|
+
id: project.id,
|
|
27
|
+
name: project.path_with_namespace,
|
|
28
|
+
nameWithoutNamespace: project.path,
|
|
29
|
+
group: getGroupName(project),
|
|
30
|
+
archived: project.archived,
|
|
31
|
+
default_branch: project.default_branch || 'master',
|
|
32
|
+
url: project.web_url,
|
|
33
|
+
topics: (project.topics || []).map((t) => t.toLowerCase())
|
|
34
|
+
};
|
|
39
35
|
}
|
|
40
|
-
|
|
41
36
|
function getGroupName(project) {
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
const pathWithNameSpace = project.path_with_namespace;
|
|
38
|
+
return pathWithNameSpace.split('/')[0];
|
|
44
39
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
40
|
+
function includeRegexFilter(gitlab) {
|
|
41
|
+
return (project) => {
|
|
42
|
+
if (gitlab.projects?.include) {
|
|
43
|
+
const includeRegex = new RegExp(gitlab.projects.include, "i");
|
|
44
|
+
return includeRegex.test(project.name);
|
|
45
|
+
}
|
|
46
|
+
return true;
|
|
47
|
+
};
|
|
54
48
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
49
|
+
function excludeRegexFilter(gitlab) {
|
|
50
|
+
return (project) => {
|
|
51
|
+
if (gitlab.projects?.exclude) {
|
|
52
|
+
const excludeRegex = new RegExp(gitlab.projects.exclude, "i");
|
|
53
|
+
return !excludeRegex.test(project.name);
|
|
54
|
+
}
|
|
55
|
+
return true;
|
|
56
|
+
};
|
|
64
57
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
}
|
|
58
|
+
function archivedFilter(gitlab) {
|
|
59
|
+
return (project) => {
|
|
60
|
+
if (gitlab.ignoreArchived) {
|
|
61
|
+
return !project.archived;
|
|
62
|
+
}
|
|
63
|
+
return true;
|
|
64
|
+
};
|
|
73
65
|
}
|
package/src/gitlab/runners.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
-
import {gitlabRequest} from
|
|
2
|
-
|
|
1
|
+
import { gitlabRequest } from "./client.js";
|
|
3
2
|
export async function fetchOfflineRunners(gitlab) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
if (gitlab.offlineRunners === 'none') {
|
|
4
|
+
return {
|
|
5
|
+
offline: [],
|
|
6
|
+
totalCount: 0
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
const runners = await fetchRunners(gitlab);
|
|
10
|
+
const offline = runners.filter(r => r.status === 'offline');
|
|
11
|
+
return {
|
|
12
|
+
offline,
|
|
13
|
+
totalCount: runners.length
|
|
14
|
+
};
|
|
10
15
|
}
|
|
11
|
-
|
|
12
16
|
async function fetchRunners(gitlab) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
const runnersApi = gitlab.offlineRunners === 'all' ? '/runners/all' : '/runners';
|
|
18
|
+
const { data: runners } = await gitlabRequest(runnersApi, null, gitlab);
|
|
19
|
+
return runners.map(r => ({
|
|
20
|
+
name: r.description || r.id.toString(),
|
|
21
|
+
status: r.status
|
|
22
|
+
}));
|
|
18
23
|
}
|
package/src/less.js
CHANGED
|
@@ -1,26 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import less from 'less';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { config } from "./config.js";
|
|
5
|
+
const filename = path.join('public', 'client.less');
|
|
6
|
+
export async function serveLessAsCss(_req, res) {
|
|
7
|
+
try {
|
|
8
|
+
const source = await fs.promises.readFile(filename, 'utf-8');
|
|
9
|
+
const { css } = await less.render(withColorOverrides(source), { filename });
|
|
10
|
+
res.setHeader('content-type', 'text/css');
|
|
11
|
+
res.send(css);
|
|
12
|
+
}
|
|
13
|
+
catch (err) {
|
|
14
|
+
console.error('Failed to render client.less', err);
|
|
15
|
+
res.sendStatus(500);
|
|
16
|
+
}
|
|
18
17
|
}
|
|
19
|
-
|
|
20
18
|
function withColorOverrides(source) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
19
|
+
const { colors } = config;
|
|
20
|
+
if (!colors) {
|
|
21
|
+
return source;
|
|
22
|
+
}
|
|
23
|
+
let colorLess = '';
|
|
24
|
+
Object.keys(colors).forEach((stateName) => {
|
|
25
|
+
colorLess += `@${stateName}-color:${colors[stateName]};`;
|
|
26
|
+
});
|
|
27
|
+
return source + colorLess;
|
|
26
28
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"root":["../../src/app.ts","../../src/auth.ts","../../src/config.ts","../../src/dev-assets.ts","../../src/less.ts","../../src/common/gitlab-types.d.ts","../../src/gitlab/client.ts","../../src/gitlab/index.ts","../../src/gitlab/pipelines.ts","../../src/gitlab/projects.ts","../../src/gitlab/runners.ts"],"version":"5.9.3"}
|