@sebbo2002/gitlab-badges 7.0.5-develop.7 → 7.0.5-develop.9

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 (2) hide show
  1. package/dist/start.js.map +1 -1
  2. package/package.json +11 -10
package/dist/start.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/bin/start.ts","../src/lib/badge-response.ts","../src/lib/states.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\nimport express, { type Express } from 'express';\nimport { Server } from 'http';\n\nimport BadgeResponse from '../lib/badge-response.js';\nimport States from '../lib/states.js';\n\nclass AppServer {\n private app: Express;\n\n private badges: BadgeResponse;\n private server: Server;\n private states: States;\n constructor() {\n if (!process.env.GITLAB_URL) {\n throw new Error('Unable to start: GITLAB_URL empty');\n }\n if (!process.env.GITLAB_TOKEN) {\n throw new Error('Unable to start: GITLAB_TOKEN empty');\n }\n\n this.app = express();\n this.states = new States(\n process.env.GITLAB_URL,\n process.env.GITLAB_TOKEN,\n );\n this.badges = new BadgeResponse(process.env.BADGE_STYLE || '');\n\n this.setupRoutes();\n this.states.start();\n this.server = this.app.listen(process.env.PORT || 8888);\n\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\n }\n\n static run() {\n new AppServer();\n }\n\n getColor(coverage: number) {\n const colors = new Map([\n [10, 'red'],\n [25, 'orange'],\n [50, 'yellow'],\n [70, 'yellowgreen'],\n [85, 'green'],\n [95, 'brightgreen'],\n [100, 'brightgreen'],\n ]);\n\n for (const [min, minColor] of colors) {\n if (coverage >= min) {\n return minColor;\n }\n }\n\n return 'red';\n }\n\n setupRoutes() {\n this.app.get('/ping', (req, res) => {\n res.send('pong');\n });\n\n this.app.get('/cache', (req, res) => {\n res.send(this.states.getCachedStates());\n });\n\n this.app.get('/:projectId/:branch/build', async (req, res) => {\n const label = 'Build';\n let result;\n\n try {\n result = await this.states.getState(\n req.params.projectId,\n req.params.branch,\n );\n } catch (error) {\n this.badges.sendError(res, label, error);\n return;\n }\n\n if (['running', 'pending'].indexOf(result.status) > -1) {\n this.badges.send(res, label, result.status, 'lightgrey');\n } else if (result.status === 'success') {\n this.badges.send(res, label, 'passing', 'brightgreen');\n } else {\n this.badges.sendError(res, label, result.status);\n }\n });\n\n this.app.get('/:projectId/:branch/coverage', async (req, res) => {\n const label = 'Coverage';\n let result;\n\n try {\n result = await this.states.getState(\n req.params.projectId,\n req.params.branch,\n );\n } catch (error) {\n this.badges.sendError(res, label, error);\n return;\n }\n\n if (!result.coverage) {\n this.badges.sendError(res, label);\n return;\n }\n\n const value = parseFloat(result.coverage);\n let valueString = value.toFixed(2) + '%';\n if (value === 100) {\n valueString = '100%';\n }\n\n this.badges.send(\n res,\n 'Coverage',\n valueString,\n this.getColor(value),\n );\n });\n }\n\n async stop() {\n await new Promise((cb) => this.server.close(cb));\n this.states.stop();\n process.exit();\n }\n}\n\nAppServer.run();\n","'use strict';\n\nimport { makeBadge } from 'badge-maker';\nimport { type Response } from 'express';\n\nimport { type Style } from './types.js';\n\nexport default class BadgeResponse {\n private readonly style: Style = 'plastic';\n\n constructor(style: string) {\n const myStyle = style || process.env.BADGE_STYLE || '';\n if (\n myStyle ||\n [\n 'flat',\n 'flat-square',\n 'for-the-badge',\n 'plastic',\n 'social',\n ].includes(myStyle)\n ) {\n this.style = myStyle as Style;\n }\n }\n\n send(res: Response, label: string, value: string, color: string): void {\n this.sendBadge(\n res,\n makeBadge({\n color: color,\n label,\n message: value || '-',\n style: this.style,\n }),\n );\n }\n\n sendBadge(res: Response, badge: string): void {\n if (!badge) {\n res.sendStatus(404);\n return;\n }\n\n res.set('Content-Type', 'image/svg+xml');\n res.set(\n 'Cache-Control',\n 'private, no-cache, no-store, must-revalidate',\n );\n res.set('Expires', '-1');\n res.set('Pragma', 'no-cache');\n res.send(badge);\n }\n\n sendError(res: Response, label: string, error?: string | unknown): void {\n this.sendBadge(\n res,\n makeBadge({\n color: 'red',\n label,\n message: String(error) || 'error',\n style: this.style,\n }),\n );\n }\n}\n","'use strict';\n\nimport { type StateCacheItem, type StateCachePipeline } from './types.js';\n\nexport default class GitLabStateHelper {\n private cache: Record<string, StateCacheItem> = {};\n private readonly maxCacheSize: number;\n private timeout?: NodeJS.Timeout;\n private readonly token: string;\n private readonly url: string;\n\n constructor(url?: string, token?: string) {\n this.url = url || process.env.GITLAB_URL || '';\n if (\n typeof this.url !== 'string' ||\n (this.url.substr(0, 7) !== 'http://' &&\n this.url.substr(0, 8) !== 'https://')\n ) {\n throw new Error('Invalid url!');\n }\n\n this.token = token || process.env.GITLAB_TOKEN || '';\n if (this.token.length < 5) {\n throw new Error('Invalid token!');\n }\n\n this.maxCacheSize =\n parseInt(process.env.MAX_CACHE_SIZE ?? '50', 10);\n\n if (isNaN(this.maxCacheSize)) {\n throw new Error('Invalid MAX_CACHE_SIZE value!');\n }\n }\n\n public getCachedStates(): Record<string, StateCacheItem> {\n return this.cache;\n }\n\n public async getState(\n projectId: string,\n branch: string,\n ): Promise<StateCachePipeline> {\n if (!this.maxCacheSize || !this.cache[projectId]?.pipelines[branch]) {\n await this.refreshState(projectId);\n }\n\n if (this.cache[projectId]?.pipelines[branch]) {\n this.cache[projectId].lastHit = new Date().getTime();\n return this.cache[projectId].pipelines[branch];\n }\n\n throw new Error('Unable to find branch!');\n }\n\n public start(): void {\n if (!this.maxCacheSize) return;\n if (!this.timeout) {\n this.refreshNextState();\n }\n }\n\n public stop(): void {\n if (!this.maxCacheSize) return;\n if (this.timeout) {\n clearTimeout(this.timeout);\n delete this.timeout;\n }\n }\n\n protected refreshNextState(): void {\n if (!this.maxCacheSize) return;\n const now = new Date().getTime();\n let count = 0;\n let when = 120000;\n let mostUnused;\n let inms;\n\n this.stop();\n\n for (const i in this.cache) {\n if (Object.prototype.hasOwnProperty.call(this.cache, i)) {\n const item = this.cache[i];\n inms = item.validTill - now;\n count += 1;\n\n if (item.validTill <= now) {\n this.refreshState(i).catch(() => {\n // ignore error\n });\n } else if (inms < when) {\n when = inms;\n }\n\n const mostUsedItem = mostUnused ? this.cache[mostUnused] : null;\n if (\n !mostUsedItem ||\n !mostUsedItem.lastHit ||\n (item.lastHit && item.lastHit < mostUsedItem.lastHit)\n ) {\n mostUnused = i;\n }\n }\n }\n\n if (count > this.maxCacheSize && mostUnused) {\n delete this.cache[mostUnused];\n }\n\n this.timeout = setTimeout(() => this.refreshNextState(), when);\n }\n\n protected async refreshState(projectId: string): Promise<void> {\n const body = await this.request(\n '/projects/' + projectId + '/pipelines?scope=branches',\n );\n if (!Array.isArray(body)) {\n throw new Error('Unexpected answer from GitLab.');\n }\n\n let error: unknown;\n let cacheDuration = 5;\n const result: Record<string, StateCachePipeline> = {};\n await Promise.all(\n body.map(async (pipeline) => {\n if (['pending', 'running'].includes(pipeline.status)) {\n cacheDuration = 0.5;\n }\n if (['canceled', 'failed'].includes(pipeline.status)) {\n cacheDuration = Math.min(cacheDuration, 2);\n }\n\n try {\n const extendedPipeline = (await this.request(\n '/projects/' + projectId + '/pipelines/' + pipeline.id,\n )) as { coverage: null | string };\n result[pipeline.ref] = {\n coverage: extendedPipeline.coverage || undefined,\n status: pipeline.status,\n };\n } catch (err) {\n error = err;\n result[pipeline.ref] = {\n coverage: undefined,\n status: pipeline.status,\n };\n }\n }),\n );\n\n this.cache[projectId] = {\n lastHit: this.cache[projectId]?.lastHit || new Date().getTime(),\n lastSync: new Date().getTime(),\n pipelines: result,\n validTill: new Date().getTime() + 1000 * 60 * cacheDuration,\n };\n\n if (this.timeout) {\n this.refreshNextState();\n }\n\n if (error) {\n throw error;\n }\n }\n\n protected async request(path: string): Promise<unknown> {\n const result = await fetch(this.url + '/api/v4' + path, {\n headers: {\n 'PRIVATE-TOKEN': this.token,\n },\n });\n\n return result.json();\n }\n}\n"],"mappings":"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","names":["express","makeBadge","BadgeResponse","style","myStyle","res","label","value","color","makeBadge","badge","error","GitLabStateHelper","url","token","projectId","branch","now","count","when","mostUnused","inms","item","mostUsedItem","body","error","cacheDuration","result","pipeline","extendedPipeline","err","path","AppServer","_AppServer","express","GitLabStateHelper","BadgeResponse","coverage","colors","min","minColor","req","res","label","result","error","value","valueString","cb"]}
1
+ {"version":3,"sources":["../src/bin/start.ts","../src/lib/badge-response.ts","../src/lib/states.ts"],"sourcesContent":["#!/usr/bin/env node\n'use strict';\n\nimport express, { type Express } from 'express';\nimport { Server } from 'http';\n\nimport BadgeResponse from '../lib/badge-response.js';\nimport States from '../lib/states.js';\n\nclass AppServer {\n private app: Express;\n\n private badges: BadgeResponse;\n private server: Server;\n private states: States;\n constructor() {\n if (!process.env.GITLAB_URL) {\n throw new Error('Unable to start: GITLAB_URL empty');\n }\n if (!process.env.GITLAB_TOKEN) {\n throw new Error('Unable to start: GITLAB_TOKEN empty');\n }\n\n this.app = express();\n this.states = new States(\n process.env.GITLAB_URL,\n process.env.GITLAB_TOKEN,\n );\n this.badges = new BadgeResponse(process.env.BADGE_STYLE || '');\n\n this.setupRoutes();\n this.states.start();\n this.server = this.app.listen(process.env.PORT || 8888);\n\n process.on('SIGINT', () => this.stop());\n process.on('SIGTERM', () => this.stop());\n }\n\n static run() {\n new AppServer();\n }\n\n getColor(coverage: number) {\n const colors = new Map([\n [10, 'red'],\n [25, 'orange'],\n [50, 'yellow'],\n [70, 'yellowgreen'],\n [85, 'green'],\n [95, 'brightgreen'],\n [100, 'brightgreen'],\n ]);\n\n for (const [min, minColor] of colors) {\n if (coverage >= min) {\n return minColor;\n }\n }\n\n return 'red';\n }\n\n setupRoutes() {\n this.app.get('/ping', (req, res) => {\n res.send('pong');\n });\n\n this.app.get('/cache', (req, res) => {\n res.send(this.states.getCachedStates());\n });\n\n this.app.get('/:projectId/:branch/build', async (req, res) => {\n const label = 'Build';\n let result;\n\n try {\n result = await this.states.getState(\n req.params.projectId,\n req.params.branch,\n );\n } catch (error) {\n this.badges.sendError(res, label, error);\n return;\n }\n\n if (['running', 'pending'].indexOf(result.status) > -1) {\n this.badges.send(res, label, result.status, 'lightgrey');\n } else if (result.status === 'success') {\n this.badges.send(res, label, 'passing', 'brightgreen');\n } else {\n this.badges.sendError(res, label, result.status);\n }\n });\n\n this.app.get('/:projectId/:branch/coverage', async (req, res) => {\n const label = 'Coverage';\n let result;\n\n try {\n result = await this.states.getState(\n req.params.projectId,\n req.params.branch,\n );\n } catch (error) {\n this.badges.sendError(res, label, error);\n return;\n }\n\n if (!result.coverage) {\n this.badges.sendError(res, label);\n return;\n }\n\n const value = parseFloat(result.coverage);\n let valueString = value.toFixed(2) + '%';\n if (value === 100) {\n valueString = '100%';\n }\n\n this.badges.send(\n res,\n 'Coverage',\n valueString,\n this.getColor(value),\n );\n });\n }\n\n async stop() {\n await new Promise((cb) => this.server.close(cb));\n this.states.stop();\n process.exit();\n }\n}\n\nAppServer.run();\n","'use strict';\n\nimport { makeBadge } from 'badge-maker';\nimport { type Response } from 'express';\n\nimport { type Style } from './types.js';\n\nexport default class BadgeResponse {\n private readonly style: Style = 'plastic';\n\n constructor(style: string) {\n const myStyle = style || process.env.BADGE_STYLE || '';\n if (\n myStyle ||\n [\n 'flat',\n 'flat-square',\n 'for-the-badge',\n 'plastic',\n 'social',\n ].includes(myStyle)\n ) {\n this.style = myStyle as Style;\n }\n }\n\n send(res: Response, label: string, value: string, color: string): void {\n this.sendBadge(\n res,\n makeBadge({\n color: color,\n label,\n message: value || '-',\n style: this.style,\n }),\n );\n }\n\n sendBadge(res: Response, badge: string): void {\n if (!badge) {\n res.sendStatus(404);\n return;\n }\n\n res.set('Content-Type', 'image/svg+xml');\n res.set(\n 'Cache-Control',\n 'private, no-cache, no-store, must-revalidate',\n );\n res.set('Expires', '-1');\n res.set('Pragma', 'no-cache');\n res.send(badge);\n }\n\n sendError(res: Response, label: string, error?: string | unknown): void {\n this.sendBadge(\n res,\n makeBadge({\n color: 'red',\n label,\n message: String(error) || 'error',\n style: this.style,\n }),\n );\n }\n}\n","'use strict';\n\nimport { type StateCacheItem, type StateCachePipeline } from './types.js';\n\nexport default class GitLabStateHelper {\n private cache: Record<string, StateCacheItem> = {};\n private readonly maxCacheSize: number;\n private timeout?: NodeJS.Timeout;\n private readonly token: string;\n private readonly url: string;\n\n constructor(url?: string, token?: string) {\n this.url = url || process.env.GITLAB_URL || '';\n if (\n typeof this.url !== 'string' ||\n (this.url.substr(0, 7) !== 'http://' &&\n this.url.substr(0, 8) !== 'https://')\n ) {\n throw new Error('Invalid url!');\n }\n\n this.token = token || process.env.GITLAB_TOKEN || '';\n if (this.token.length < 5) {\n throw new Error('Invalid token!');\n }\n\n this.maxCacheSize = parseInt(process.env.MAX_CACHE_SIZE ?? '50', 10);\n\n if (isNaN(this.maxCacheSize)) {\n throw new Error('Invalid MAX_CACHE_SIZE value!');\n }\n }\n\n public getCachedStates(): Record<string, StateCacheItem> {\n return this.cache;\n }\n\n public async getState(\n projectId: string,\n branch: string,\n ): Promise<StateCachePipeline> {\n if (!this.maxCacheSize || !this.cache[projectId]?.pipelines[branch]) {\n await this.refreshState(projectId);\n }\n\n if (this.cache[projectId]?.pipelines[branch]) {\n this.cache[projectId].lastHit = new Date().getTime();\n return this.cache[projectId].pipelines[branch];\n }\n\n throw new Error('Unable to find branch!');\n }\n\n public start(): void {\n if (!this.maxCacheSize) return;\n if (!this.timeout) {\n this.refreshNextState();\n }\n }\n\n public stop(): void {\n if (!this.maxCacheSize) return;\n if (this.timeout) {\n clearTimeout(this.timeout);\n delete this.timeout;\n }\n }\n\n protected refreshNextState(): void {\n if (!this.maxCacheSize) return;\n const now = new Date().getTime();\n let count = 0;\n let when = 120000;\n let mostUnused;\n let inms;\n\n this.stop();\n\n for (const i in this.cache) {\n if (Object.prototype.hasOwnProperty.call(this.cache, i)) {\n const item = this.cache[i];\n inms = item.validTill - now;\n count += 1;\n\n if (item.validTill <= now) {\n this.refreshState(i).catch(() => {\n // ignore error\n });\n } else if (inms < when) {\n when = inms;\n }\n\n const mostUsedItem = mostUnused ? this.cache[mostUnused] : null;\n if (\n !mostUsedItem ||\n !mostUsedItem.lastHit ||\n (item.lastHit && item.lastHit < mostUsedItem.lastHit)\n ) {\n mostUnused = i;\n }\n }\n }\n\n if (count > this.maxCacheSize && mostUnused) {\n delete this.cache[mostUnused];\n }\n\n this.timeout = setTimeout(() => this.refreshNextState(), when);\n }\n\n protected async refreshState(projectId: string): Promise<void> {\n const body = await this.request(\n '/projects/' + projectId + '/pipelines?scope=branches',\n );\n if (!Array.isArray(body)) {\n throw new Error('Unexpected answer from GitLab.');\n }\n\n let error: unknown;\n let cacheDuration = 5;\n const result: Record<string, StateCachePipeline> = {};\n await Promise.all(\n body.map(async (pipeline) => {\n if (['pending', 'running'].includes(pipeline.status)) {\n cacheDuration = 0.5;\n }\n if (['canceled', 'failed'].includes(pipeline.status)) {\n cacheDuration = Math.min(cacheDuration, 2);\n }\n\n try {\n const extendedPipeline = (await this.request(\n '/projects/' + projectId + '/pipelines/' + pipeline.id,\n )) as { coverage: null | string };\n result[pipeline.ref] = {\n coverage: extendedPipeline.coverage || undefined,\n status: pipeline.status,\n };\n } catch (err) {\n error = err;\n result[pipeline.ref] = {\n coverage: undefined,\n status: pipeline.status,\n };\n }\n }),\n );\n\n this.cache[projectId] = {\n lastHit: this.cache[projectId]?.lastHit || new Date().getTime(),\n lastSync: new Date().getTime(),\n pipelines: result,\n validTill: new Date().getTime() + 1000 * 60 * cacheDuration,\n };\n\n if (this.timeout) {\n this.refreshNextState();\n }\n\n if (error) {\n throw error;\n }\n }\n\n protected async request(path: string): Promise<unknown> {\n const result = await fetch(this.url + '/api/v4' + path, {\n headers: {\n 'PRIVATE-TOKEN': this.token,\n },\n });\n\n return result.json();\n }\n}\n"],"mappings":";AAGA,OAAOA,MAA+B,UCDtC,OAAS,aAAAC,MAAiB,cAC1B,MAA8B,UAI9B,IAAqBC,EAArB,KAAmC,CACd,MAAe,UAEhC,YAAYC,EAAe,CACvB,IAAMC,EAAUD,GAAS,QAAQ,IAAI,aAAe,IAEhDC,GACA,CACI,OACA,cACA,gBACA,UACA,QACJ,EAAE,SAASA,CAAO,KAElB,KAAK,MAAQA,EAErB,CAEA,KAAKC,EAAeC,EAAeC,EAAeC,EAAqB,CACnE,KAAK,UACDH,EACAI,EAAU,CACN,MAAOD,EACP,MAAAF,EACA,QAASC,GAAS,IAClB,MAAO,KAAK,KAChB,CAAC,CACL,CACJ,CAEA,UAAUF,EAAeK,EAAqB,CAC1C,GAAI,CAACA,EAAO,CACRL,EAAI,WAAW,GAAG,EAClB,MACJ,CAEAA,EAAI,IAAI,eAAgB,eAAe,EACvCA,EAAI,IACA,gBACA,8CACJ,EACAA,EAAI,IAAI,UAAW,IAAI,EACvBA,EAAI,IAAI,SAAU,UAAU,EAC5BA,EAAI,KAAKK,CAAK,CAClB,CAEA,UAAUL,EAAeC,EAAeK,EAAgC,CACpE,KAAK,UACDN,EACAI,EAAU,CACN,MAAO,MACP,MAAAH,EACA,QAAS,OAAOK,CAAK,GAAK,QAC1B,MAAO,KAAK,KAChB,CAAC,CACL,CACJ,CACJ,EC7DA,IAAqBC,EAArB,KAAuC,CAC3B,MAAwC,CAAC,EAChC,aACT,QACS,MACA,IAEjB,YAAYC,EAAcC,EAAgB,CAEtC,GADA,KAAK,IAAMD,GAAO,QAAQ,IAAI,YAAc,GAExC,OAAO,KAAK,KAAQ,UACnB,KAAK,IAAI,OAAO,EAAG,CAAC,IAAM,WACvB,KAAK,IAAI,OAAO,EAAG,CAAC,IAAM,WAE9B,MAAM,IAAI,MAAM,cAAc,EAIlC,GADA,KAAK,MAAQC,GAAS,QAAQ,IAAI,cAAgB,GAC9C,KAAK,MAAM,OAAS,EACpB,MAAM,IAAI,MAAM,gBAAgB,EAKpC,GAFA,KAAK,aAAe,SAAS,QAAQ,IAAI,gBAAkB,KAAM,EAAE,EAE/D,MAAM,KAAK,YAAY,EACvB,MAAM,IAAI,MAAM,+BAA+B,CAEvD,CAEO,iBAAkD,CACrD,OAAO,KAAK,KAChB,CAEA,MAAa,SACTC,EACAC,EAC2B,CAK3B,IAJI,CAAC,KAAK,cAAgB,CAAC,KAAK,MAAMD,CAAS,GAAG,UAAUC,CAAM,IAC9D,MAAM,KAAK,aAAaD,CAAS,EAGjC,KAAK,MAAMA,CAAS,GAAG,UAAUC,CAAM,EACvC,YAAK,MAAMD,CAAS,EAAE,QAAU,IAAI,KAAK,EAAE,QAAQ,EAC5C,KAAK,MAAMA,CAAS,EAAE,UAAUC,CAAM,EAGjD,MAAM,IAAI,MAAM,wBAAwB,CAC5C,CAEO,OAAc,CACZ,KAAK,eACL,KAAK,SACN,KAAK,iBAAiB,EAE9B,CAEO,MAAa,CACX,KAAK,cACN,KAAK,UACL,aAAa,KAAK,OAAO,EACzB,OAAO,KAAK,QAEpB,CAEU,kBAAyB,CAC/B,GAAI,CAAC,KAAK,aAAc,OACxB,IAAMC,EAAM,IAAI,KAAK,EAAE,QAAQ,EAC3BC,EAAQ,EACRC,EAAO,KACPC,EACAC,EAEJ,KAAK,KAAK,EAEV,QAAW,KAAK,KAAK,MACjB,GAAI,OAAO,UAAU,eAAe,KAAK,KAAK,MAAO,CAAC,EAAG,CACrD,IAAMC,EAAO,KAAK,MAAM,CAAC,EACzBD,EAAOC,EAAK,UAAYL,EACxBC,GAAS,EAELI,EAAK,WAAaL,EAClB,KAAK,aAAa,CAAC,EAAE,MAAM,IAAM,CAEjC,CAAC,EACMI,EAAOF,IACdA,EAAOE,GAGX,IAAME,EAAeH,EAAa,KAAK,MAAMA,CAAU,EAAI,MAEvD,CAACG,GACD,CAACA,EAAa,SACbD,EAAK,SAAWA,EAAK,QAAUC,EAAa,WAE7CH,EAAa,EAErB,CAGAF,EAAQ,KAAK,cAAgBE,GAC7B,OAAO,KAAK,MAAMA,CAAU,EAGhC,KAAK,QAAU,WAAW,IAAM,KAAK,iBAAiB,EAAGD,CAAI,CACjE,CAEA,MAAgB,aAAaJ,EAAkC,CAC3D,IAAMS,EAAO,MAAM,KAAK,QACpB,aAAeT,EAAY,2BAC/B,EACA,GAAI,CAAC,MAAM,QAAQS,CAAI,EACnB,MAAM,IAAI,MAAM,gCAAgC,EAGpD,IAAIC,EACAC,EAAgB,EACdC,EAA6C,CAAC,EAuCpD,GAtCA,MAAM,QAAQ,IACVH,EAAK,IAAI,MAAOI,GAAa,CACrB,CAAC,UAAW,SAAS,EAAE,SAASA,EAAS,MAAM,IAC/CF,EAAgB,IAEhB,CAAC,WAAY,QAAQ,EAAE,SAASE,EAAS,MAAM,IAC/CF,EAAgB,KAAK,IAAIA,EAAe,CAAC,GAG7C,GAAI,CACA,IAAMG,EAAoB,MAAM,KAAK,QACjC,aAAed,EAAY,cAAgBa,EAAS,EACxD,EACAD,EAAOC,EAAS,GAAG,EAAI,CACnB,SAAUC,EAAiB,UAAY,OACvC,OAAQD,EAAS,MACrB,CACJ,OAASE,EAAK,CACVL,EAAQK,EACRH,EAAOC,EAAS,GAAG,EAAI,CACnB,SAAU,OACV,OAAQA,EAAS,MACrB,CACJ,CACJ,CAAC,CACL,EAEA,KAAK,MAAMb,CAAS,EAAI,CACpB,QAAS,KAAK,MAAMA,CAAS,GAAG,SAAW,IAAI,KAAK,EAAE,QAAQ,EAC9D,SAAU,IAAI,KAAK,EAAE,QAAQ,EAC7B,UAAWY,EACX,UAAW,IAAI,KAAK,EAAE,QAAQ,EAAI,IAAO,GAAKD,CAClD,EAEI,KAAK,SACL,KAAK,iBAAiB,EAGtBD,EACA,MAAMA,CAEd,CAEA,MAAgB,QAAQM,EAAgC,CAOpD,OANe,MAAM,MAAM,KAAK,IAAM,UAAYA,EAAM,CACpD,QAAS,CACL,gBAAiB,KAAK,KAC1B,CACJ,CAAC,GAEa,KAAK,CACvB,CACJ,EFpKA,IAAMC,EAAN,MAAMC,CAAU,CACJ,IAEA,OACA,OACA,OACR,aAAc,CACV,GAAI,CAAC,QAAQ,IAAI,WACb,MAAM,IAAI,MAAM,mCAAmC,EAEvD,GAAI,CAAC,QAAQ,IAAI,aACb,MAAM,IAAI,MAAM,qCAAqC,EAGzD,KAAK,IAAMC,EAAQ,EACnB,KAAK,OAAS,IAAIC,EACd,QAAQ,IAAI,WACZ,QAAQ,IAAI,YAChB,EACA,KAAK,OAAS,IAAIC,EAAc,QAAQ,IAAI,aAAe,EAAE,EAE7D,KAAK,YAAY,EACjB,KAAK,OAAO,MAAM,EAClB,KAAK,OAAS,KAAK,IAAI,OAAO,QAAQ,IAAI,MAAQ,IAAI,EAEtD,QAAQ,GAAG,SAAU,IAAM,KAAK,KAAK,CAAC,EACtC,QAAQ,GAAG,UAAW,IAAM,KAAK,KAAK,CAAC,CAC3C,CAEA,OAAO,KAAM,CACT,IAAIH,CACR,CAEA,SAASI,EAAkB,CACvB,IAAMC,EAAS,IAAI,IAAI,CACnB,CAAC,GAAI,KAAK,EACV,CAAC,GAAI,QAAQ,EACb,CAAC,GAAI,QAAQ,EACb,CAAC,GAAI,aAAa,EAClB,CAAC,GAAI,OAAO,EACZ,CAAC,GAAI,aAAa,EAClB,CAAC,IAAK,aAAa,CACvB,CAAC,EAED,OAAW,CAACC,EAAKC,CAAQ,IAAKF,EAC1B,GAAID,GAAYE,EACZ,OAAOC,EAIf,MAAO,KACX,CAEA,aAAc,CACV,KAAK,IAAI,IAAI,QAAS,CAACC,EAAKC,IAAQ,CAChCA,EAAI,KAAK,MAAM,CACnB,CAAC,EAED,KAAK,IAAI,IAAI,SAAU,CAACD,EAAKC,IAAQ,CACjCA,EAAI,KAAK,KAAK,OAAO,gBAAgB,CAAC,CAC1C,CAAC,EAED,KAAK,IAAI,IAAI,4BAA6B,MAAOD,EAAKC,IAAQ,CAC1D,IAAMC,EAAQ,QACVC,EAEJ,GAAI,CACAA,EAAS,MAAM,KAAK,OAAO,SACvBH,EAAI,OAAO,UACXA,EAAI,OAAO,MACf,CACJ,OAASI,EAAO,CACZ,KAAK,OAAO,UAAUH,EAAKC,EAAOE,CAAK,EACvC,MACJ,CAEI,CAAC,UAAW,SAAS,EAAE,QAAQD,EAAO,MAAM,EAAI,GAChD,KAAK,OAAO,KAAKF,EAAKC,EAAOC,EAAO,OAAQ,WAAW,EAChDA,EAAO,SAAW,UACzB,KAAK,OAAO,KAAKF,EAAKC,EAAO,UAAW,aAAa,EAErD,KAAK,OAAO,UAAUD,EAAKC,EAAOC,EAAO,MAAM,CAEvD,CAAC,EAED,KAAK,IAAI,IAAI,+BAAgC,MAAOH,EAAKC,IAAQ,CAC7D,IAAMC,EAAQ,WACVC,EAEJ,GAAI,CACAA,EAAS,MAAM,KAAK,OAAO,SACvBH,EAAI,OAAO,UACXA,EAAI,OAAO,MACf,CACJ,OAASI,EAAO,CACZ,KAAK,OAAO,UAAUH,EAAKC,EAAOE,CAAK,EACvC,MACJ,CAEA,GAAI,CAACD,EAAO,SAAU,CAClB,KAAK,OAAO,UAAUF,EAAKC,CAAK,EAChC,MACJ,CAEA,IAAMG,EAAQ,WAAWF,EAAO,QAAQ,EACpCG,EAAcD,EAAM,QAAQ,CAAC,EAAI,IACjCA,IAAU,MACVC,EAAc,QAGlB,KAAK,OAAO,KACRL,EACA,WACAK,EACA,KAAK,SAASD,CAAK,CACvB,CACJ,CAAC,CACL,CAEA,MAAM,MAAO,CACT,MAAM,IAAI,QAASE,GAAO,KAAK,OAAO,MAAMA,CAAE,CAAC,EAC/C,KAAK,OAAO,KAAK,EACjB,QAAQ,KAAK,CACjB,CACJ,EAEAhB,EAAU,IAAI","names":["express","makeBadge","BadgeResponse","style","myStyle","res","label","value","color","makeBadge","badge","error","GitLabStateHelper","url","token","projectId","branch","now","count","when","mostUnused","inms","item","mostUsedItem","body","error","cacheDuration","result","pipeline","extendedPipeline","err","path","AppServer","_AppServer","express","GitLabStateHelper","BadgeResponse","coverage","colors","min","minColor","req","res","label","result","error","value","valueString","cb"]}
package/package.json CHANGED
@@ -12,34 +12,35 @@
12
12
  },
13
13
  "description": "It's like a very tiny shields.io (without website) for your private GitLab instance…",
14
14
  "devDependencies": {
15
- "@eslint/js": "^9.39.2",
15
+ "@eslint/js": "^10.0.1",
16
16
  "@sebbo2002/semantic-release-docker": "^6.0.2",
17
17
  "@semantic-release/changelog": "^6.0.3",
18
18
  "@semantic-release/exec": "^7.1.0",
19
19
  "@semantic-release/git": "^10.0.1",
20
- "@semantic-release/npm": "^13.1.3",
20
+ "@semantic-release/npm": "^13.1.4",
21
21
  "@types/express": "^5.0.6",
22
22
  "@types/mocha": "^10.0.10",
23
- "@types/node": "^25.0.9",
23
+ "@types/node": "^25.2.3",
24
24
  "c8": "^10.1.3",
25
- "eslint": "^9.39.2",
25
+ "eslint": "^10.0.0",
26
26
  "eslint-config-prettier": "^10.1.8",
27
- "eslint-plugin-jsonc": "^2.21.0",
28
- "eslint-plugin-perfectionist": "^5.3.1",
27
+ "eslint-plugin-jsonc": "^2.21.1",
28
+ "eslint-plugin-perfectionist": "^5.5.0",
29
29
  "esm": "^3.2.25",
30
+ "globals": "^17.3.0",
30
31
  "husky": "^9.1.7",
31
32
  "license-checker": "^25.0.1",
32
33
  "mocha": "^11.7.5",
33
34
  "mochawesome": "^7.1.4",
34
35
  "prettier": "^3.8.1",
35
- "semantic-release": "^25.0.2",
36
+ "semantic-release": "^25.0.3",
36
37
  "semantic-release-license": "^1.0.3",
37
38
  "source-map-support": "^0.5.21",
38
39
  "tsup": "^8.5.1",
39
40
  "tsx": "^4.21.0",
40
- "typedoc": "^0.28.16",
41
+ "typedoc": "^0.28.17",
41
42
  "typescript": "^5.9.3",
42
- "typescript-eslint": "^8.54.0"
43
+ "typescript-eslint": "^8.56.0"
43
44
  },
44
45
  "engines": {
45
46
  "node": "20 || >=22.0.0"
@@ -65,5 +66,5 @@
65
66
  "test": "mocha"
66
67
  },
67
68
  "type": "module",
68
- "version": "7.0.5-develop.7"
69
+ "version": "7.0.5-develop.9"
69
70
  }