gitlab-badge-proxy 0.1.3 → 0.1.4
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 +12 -0
- package/dist/src/app.d.ts.map +1 -1
- package/dist/src/app.js +128 -52
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,6 +76,18 @@ explicitly whitelisted. The ref segment is free-form (any branch of a whiteliste
|
|
|
76
76
|
the fixed `GITLAB_REF` used by the flat `/pipeline.svg` / `/coverage.svg` routes above. The coverage
|
|
77
77
|
route also accepts `?job=<job-name>`, same as the flat route.
|
|
78
78
|
|
|
79
|
+
## Project-scoped badges beyond pipeline/coverage/release
|
|
80
|
+
|
|
81
|
+
The rest of the [Routes](#routes) above — stars, forks, contributors, issues, merge requests, last
|
|
82
|
+
commit, license, tag, languages, language count, labels, go.mod version — aren't part of GitLab's own
|
|
83
|
+
badge system, so there's no GitLab-native URL shape to mirror for them. This proxy has its own:
|
|
84
|
+
`GET /<group/project>/badges/<name>.svg`, e.g. `https://badges.yourdomain.tld/ardenslab/pm2-dash/badges/stars.svg`
|
|
85
|
+
or `.../badges/issues.svg?state=opened&labels=bug`. `<name>` is one of `stars`, `forks`,
|
|
86
|
+
`contributors`, `issues`, `merge-requests`, `last-commit`, `license`, `tag`, `languages`,
|
|
87
|
+
`languages-count`, `labels`, `go-mod` — same query params as the matching flat route (`labels.svg`
|
|
88
|
+
still needs `?name=`). Gated by the same `GITLAB_PROJECTS` whitelist as the GitLab-shaped routes
|
|
89
|
+
above; an unlisted project 404s the same way.
|
|
90
|
+
|
|
79
91
|
## Badge preview page
|
|
80
92
|
|
|
81
93
|
`GET /debug/<group/project>?ref=<branch>` renders an HTML page with every badge this proxy can
|
package/dist/src/app.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAoBtD,OAAO,EACL,eAAe,EAkBhB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAgB,MAAM,MAAM,CAAC;AAE1C,OAAO,EAAc,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,EAAE,UAAU,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAoBtD,OAAO,EACL,eAAe,EAkBhB,MAAM,YAAY,CAAC;AAkcpB,wBAAgB,SAAS,CAAC,SAAS,GAAE,MAAM,MAAmB,GAAG,IAAI,CAoOpE;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAC3B,eAAO,MAAM,GAAG,4EAAc,CAAC"}
|
package/dist/src/app.js
CHANGED
|
@@ -12,6 +12,27 @@ const UNKNOWN = { label: "gitlab", message: "unknown", color: "lightgrey" };
|
|
|
12
12
|
const BADGE_PATH_PATTERN = /^(.+)\/badges\/([^/]+)\/(pipeline|coverage)\.svg$/;
|
|
13
13
|
/** Matches GitLab's latest-release badge shape: `<project path>/-/badges/release.svg` (no ref). */
|
|
14
14
|
const RELEASE_PATH_PATTERN = /^(.+)\/-\/badges\/release\.svg$/;
|
|
15
|
+
/**
|
|
16
|
+
* This proxy's own badge shape (not one GitLab itself has): `<project path>/badges/<name>.svg`,
|
|
17
|
+
* for everything besides pipeline/coverage/release that isn't tied to a ref. Lets any whitelisted
|
|
18
|
+
* project use these the same way it already can use the GitLab-native ones above, instead of only
|
|
19
|
+
* the single fixed GITLAB_PROJECT getting them (via the flat /<name>.svg routes).
|
|
20
|
+
*/
|
|
21
|
+
const SIMPLE_BADGE_NAMES = [
|
|
22
|
+
"stars",
|
|
23
|
+
"forks",
|
|
24
|
+
"contributors",
|
|
25
|
+
"issues",
|
|
26
|
+
"merge-requests",
|
|
27
|
+
"last-commit",
|
|
28
|
+
"license",
|
|
29
|
+
"tag",
|
|
30
|
+
"languages",
|
|
31
|
+
"languages-count",
|
|
32
|
+
"labels",
|
|
33
|
+
"go-mod",
|
|
34
|
+
];
|
|
35
|
+
const SIMPLE_BADGE_PATH_PATTERN = new RegExp(`^(.+)\\/badges\\/(${SIMPLE_BADGE_NAMES.join("|")})\\.svg$`);
|
|
15
36
|
/**
|
|
16
37
|
* Known-safe `/api/v4/projects/:id/<subpath>` suffixes: just enough of the real GitLab REST API
|
|
17
38
|
* for shields.io-style "point gitlab_url at a different host" badges (release/tag/pipeline/
|
|
@@ -166,6 +187,18 @@ function renderDebugPage(project, ref, origin) {
|
|
|
166
187
|
{ label: "pipeline", src: `/${project}/badges/${ref}/pipeline.svg` },
|
|
167
188
|
{ label: "coverage", src: `/${project}/badges/${ref}/coverage.svg` },
|
|
168
189
|
{ label: "release", src: `/${project}/-/badges/release.svg` },
|
|
190
|
+
{ label: "stars", src: `/${project}/badges/stars.svg` },
|
|
191
|
+
{ label: "forks", src: `/${project}/badges/forks.svg` },
|
|
192
|
+
{ label: "contributors", src: `/${project}/badges/contributors.svg` },
|
|
193
|
+
{ label: "issues", src: `/${project}/badges/issues.svg` },
|
|
194
|
+
{ label: "merge requests", src: `/${project}/badges/merge-requests.svg` },
|
|
195
|
+
{ label: "last commit", src: `/${project}/badges/last-commit.svg` },
|
|
196
|
+
{ label: "license", src: `/${project}/badges/license.svg` },
|
|
197
|
+
{ label: "tag", src: `/${project}/badges/tag.svg` },
|
|
198
|
+
{ label: "top language", src: `/${project}/badges/languages.svg` },
|
|
199
|
+
{ label: "language count", src: `/${project}/badges/languages-count.svg` },
|
|
200
|
+
{ label: "label color (bug)", src: `/${project}/badges/labels.svg?name=bug` },
|
|
201
|
+
{ label: "go.mod version", src: `/${project}/badges/go-mod.svg` },
|
|
169
202
|
];
|
|
170
203
|
const nativeDefaultBadges = [
|
|
171
204
|
{ label: "pipeline", src: "/pipeline.svg" },
|
|
@@ -286,6 +319,54 @@ async function resolveReleaseSpec(config, projectPath) {
|
|
|
286
319
|
return releaseToBadge(release);
|
|
287
320
|
});
|
|
288
321
|
}
|
|
322
|
+
/** Computes one of SIMPLE_BADGE_NAMES's badges for `projectPath` - shared by the flat `/<name>.svg`
|
|
323
|
+
* routes (always `config.project`) and the project-scoped `/:project/badges/<name>.svg` routes
|
|
324
|
+
* (any whitelisted project), so the two stay identical instead of duplicating this per route. */
|
|
325
|
+
async function computeSimpleBadge(config, projectPath, name, c) {
|
|
326
|
+
switch (name) {
|
|
327
|
+
case "stars":
|
|
328
|
+
return starsToBadge(await getStarsCount(config, projectPath));
|
|
329
|
+
case "forks":
|
|
330
|
+
return forksToBadge(await getForksCount(config, projectPath));
|
|
331
|
+
case "contributors":
|
|
332
|
+
return contributorsToBadge(await getContributorsCount(config, projectPath));
|
|
333
|
+
case "issues": {
|
|
334
|
+
const state = c.req.query("state") ?? "all";
|
|
335
|
+
const labels = c.req.query("labels");
|
|
336
|
+
return issuesToBadge(await getIssuesCount(config, projectPath, state, labels), state, labels);
|
|
337
|
+
}
|
|
338
|
+
case "merge-requests": {
|
|
339
|
+
const state = c.req.query("state") ?? "opened";
|
|
340
|
+
const labels = c.req.query("labels");
|
|
341
|
+
return mergeRequestsToBadge(await getMergeRequestsCount(config, projectPath, state, labels), state, labels);
|
|
342
|
+
}
|
|
343
|
+
case "last-commit": {
|
|
344
|
+
const ref = c.req.query("ref") ?? config.ref;
|
|
345
|
+
const path = c.req.query("path");
|
|
346
|
+
return lastCommitToBadge(await getLastCommitDate(config, projectPath, ref, path));
|
|
347
|
+
}
|
|
348
|
+
case "license":
|
|
349
|
+
return licenseToBadge(await getLicenseName(config, projectPath));
|
|
350
|
+
case "tag":
|
|
351
|
+
return tagToBadge(await getLatestTag(config, projectPath));
|
|
352
|
+
case "languages":
|
|
353
|
+
return topLanguageToBadge(await getLanguages(config, projectPath));
|
|
354
|
+
case "languages-count":
|
|
355
|
+
return languagesCountToBadge(await getLanguages(config, projectPath));
|
|
356
|
+
case "labels": {
|
|
357
|
+
const labelName = c.req.query("name");
|
|
358
|
+
if (!labelName) {
|
|
359
|
+
throw new Error("missing required ?name= query param");
|
|
360
|
+
}
|
|
361
|
+
return labelToBadge(labelName, await getLabelColor(config, projectPath, labelName));
|
|
362
|
+
}
|
|
363
|
+
case "go-mod": {
|
|
364
|
+
const ref = c.req.query("ref") ?? config.ref;
|
|
365
|
+
const filename = c.req.query("filename") ?? "go.mod";
|
|
366
|
+
return goModVersionToBadge(await getGoModVersion(config, projectPath, ref, filename));
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
289
370
|
/** Registers a flat `/<path>.svg` route fixed to the configured default project (and ref, where relevant). */
|
|
290
371
|
function registerBadgeRoute(app, getConfig, path, fallback, context, compute) {
|
|
291
372
|
app.get(path, async (c) => {
|
|
@@ -353,40 +434,18 @@ export function createApp(getConfig = loadConfig) {
|
|
|
353
434
|
const { spec, maxAge } = await resolveReleaseSpec(config, config.project);
|
|
354
435
|
return svgResponse(await fetchBadgeSvg(spec), maxAge);
|
|
355
436
|
});
|
|
356
|
-
registerBadgeRoute(app, getConfig, "/stars.svg", UNKNOWN, "stars",
|
|
357
|
-
registerBadgeRoute(app, getConfig, "/forks.svg", UNKNOWN, "forks",
|
|
358
|
-
registerBadgeRoute(app, getConfig, "/contributors.svg", UNKNOWN, "contributors",
|
|
359
|
-
registerBadgeRoute(app, getConfig, "/issues.svg", UNKNOWN, "issues",
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
registerBadgeRoute(app, getConfig, "/
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
});
|
|
369
|
-
registerBadgeRoute(app, getConfig, "/last-commit.svg", UNKNOWN, "last commit", async (config, c) => {
|
|
370
|
-
const ref = c.req.query("ref") ?? config.ref;
|
|
371
|
-
const path = c.req.query("path");
|
|
372
|
-
return lastCommitToBadge(await getLastCommitDate(config, config.project, ref, path));
|
|
373
|
-
});
|
|
374
|
-
registerBadgeRoute(app, getConfig, "/license.svg", UNKNOWN, "license", async (config) => licenseToBadge(await getLicenseName(config, config.project)));
|
|
375
|
-
registerBadgeRoute(app, getConfig, "/tag.svg", UNKNOWN, "tag", async (config) => tagToBadge(await getLatestTag(config, config.project)));
|
|
376
|
-
registerBadgeRoute(app, getConfig, "/languages.svg", UNKNOWN, "top language", async (config) => topLanguageToBadge(await getLanguages(config, config.project)));
|
|
377
|
-
registerBadgeRoute(app, getConfig, "/languages-count.svg", UNKNOWN, "language count", async (config) => languagesCountToBadge(await getLanguages(config, config.project)));
|
|
378
|
-
registerBadgeRoute(app, getConfig, "/labels.svg", UNKNOWN, "label", async (config, c) => {
|
|
379
|
-
const name = c.req.query("name");
|
|
380
|
-
if (!name) {
|
|
381
|
-
throw new Error("missing required ?name= query param");
|
|
382
|
-
}
|
|
383
|
-
return labelToBadge(name, await getLabelColor(config, config.project, name));
|
|
384
|
-
});
|
|
385
|
-
registerBadgeRoute(app, getConfig, "/go-mod.svg", UNKNOWN, "go.mod version", async (config, c) => {
|
|
386
|
-
const ref = c.req.query("ref") ?? config.ref;
|
|
387
|
-
const filename = c.req.query("filename") ?? "go.mod";
|
|
388
|
-
return goModVersionToBadge(await getGoModVersion(config, config.project, ref, filename));
|
|
389
|
-
});
|
|
437
|
+
registerBadgeRoute(app, getConfig, "/stars.svg", UNKNOWN, "stars", (config, c) => computeSimpleBadge(config, config.project, "stars", c));
|
|
438
|
+
registerBadgeRoute(app, getConfig, "/forks.svg", UNKNOWN, "forks", (config, c) => computeSimpleBadge(config, config.project, "forks", c));
|
|
439
|
+
registerBadgeRoute(app, getConfig, "/contributors.svg", UNKNOWN, "contributors", (config, c) => computeSimpleBadge(config, config.project, "contributors", c));
|
|
440
|
+
registerBadgeRoute(app, getConfig, "/issues.svg", UNKNOWN, "issues", (config, c) => computeSimpleBadge(config, config.project, "issues", c));
|
|
441
|
+
registerBadgeRoute(app, getConfig, "/merge-requests.svg", UNKNOWN, "merge requests", (config, c) => computeSimpleBadge(config, config.project, "merge-requests", c));
|
|
442
|
+
registerBadgeRoute(app, getConfig, "/last-commit.svg", UNKNOWN, "last commit", (config, c) => computeSimpleBadge(config, config.project, "last-commit", c));
|
|
443
|
+
registerBadgeRoute(app, getConfig, "/license.svg", UNKNOWN, "license", (config, c) => computeSimpleBadge(config, config.project, "license", c));
|
|
444
|
+
registerBadgeRoute(app, getConfig, "/tag.svg", UNKNOWN, "tag", (config, c) => computeSimpleBadge(config, config.project, "tag", c));
|
|
445
|
+
registerBadgeRoute(app, getConfig, "/languages.svg", UNKNOWN, "top language", (config, c) => computeSimpleBadge(config, config.project, "languages", c));
|
|
446
|
+
registerBadgeRoute(app, getConfig, "/languages-count.svg", UNKNOWN, "language count", (config, c) => computeSimpleBadge(config, config.project, "languages-count", c));
|
|
447
|
+
registerBadgeRoute(app, getConfig, "/labels.svg", UNKNOWN, "label", (config, c) => computeSimpleBadge(config, config.project, "labels", c));
|
|
448
|
+
registerBadgeRoute(app, getConfig, "/go-mod.svg", UNKNOWN, "go.mod version", (config, c) => computeSimpleBadge(config, config.project, "go-mod", c));
|
|
390
449
|
// Visual test page: every native and shields.io badge shape this proxy can produce, for one
|
|
391
450
|
// project/ref given in the URL. /debug alone falls back to the fixed default project.
|
|
392
451
|
app.get("/debug", (c) => {
|
|
@@ -459,26 +518,43 @@ export function createApp(getConfig = loadConfig) {
|
|
|
459
518
|
return svgResponse(await fetchBadgeSvg(spec), maxAge);
|
|
460
519
|
}
|
|
461
520
|
const match = BADGE_PATH_PATTERN.exec(rest);
|
|
462
|
-
if (
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
521
|
+
if (match) {
|
|
522
|
+
const [, projectPath, ref, kind] = match;
|
|
523
|
+
let config;
|
|
524
|
+
try {
|
|
525
|
+
config = getConfig();
|
|
526
|
+
}
|
|
527
|
+
catch (error) {
|
|
528
|
+
console.error("gitlab-badge-proxy: failed to load config", error);
|
|
529
|
+
const fallback = kind === "pipeline" ? UNKNOWN_PIPELINE : UNKNOWN_COVERAGE;
|
|
530
|
+
return svgResponse(await fetchBadgeSvg(fallback), 30);
|
|
531
|
+
}
|
|
532
|
+
if (!config.allowedProjects.includes(projectPath)) {
|
|
533
|
+
return c.notFound();
|
|
534
|
+
}
|
|
535
|
+
const { spec, maxAge } = kind === "pipeline"
|
|
536
|
+
? await resolvePipelineSpec(config, projectPath, ref)
|
|
537
|
+
: await resolveCoverageSpec(config, projectPath, ref, c.req.query("job"));
|
|
538
|
+
return svgResponse(await fetchBadgeSvg(spec), maxAge);
|
|
474
539
|
}
|
|
475
|
-
|
|
476
|
-
|
|
540
|
+
const simpleMatch = SIMPLE_BADGE_PATH_PATTERN.exec(rest);
|
|
541
|
+
if (simpleMatch) {
|
|
542
|
+
const [, projectPath, name] = simpleMatch;
|
|
543
|
+
let config;
|
|
544
|
+
try {
|
|
545
|
+
config = getConfig();
|
|
546
|
+
}
|
|
547
|
+
catch (error) {
|
|
548
|
+
console.error("gitlab-badge-proxy: failed to load config", error);
|
|
549
|
+
return svgResponse(await fetchBadgeSvg(UNKNOWN), 30);
|
|
550
|
+
}
|
|
551
|
+
if (!config.allowedProjects.includes(projectPath)) {
|
|
552
|
+
return c.notFound();
|
|
553
|
+
}
|
|
554
|
+
const { spec, maxAge } = await resolveSpec(config, UNKNOWN, name, () => computeSimpleBadge(config, projectPath, name, c));
|
|
555
|
+
return svgResponse(await fetchBadgeSvg(spec), maxAge);
|
|
477
556
|
}
|
|
478
|
-
|
|
479
|
-
? await resolvePipelineSpec(config, projectPath, ref)
|
|
480
|
-
: await resolveCoverageSpec(config, projectPath, ref, c.req.query("job"));
|
|
481
|
-
return svgResponse(await fetchBadgeSvg(spec), maxAge);
|
|
557
|
+
return c.notFound();
|
|
482
558
|
});
|
|
483
559
|
return app;
|
|
484
560
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gitlab-badge-proxy",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Server-side proxy that turns private GitLab pipeline/coverage status into public SVG badges, without ever exposing the GitLab instance's URL to clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|