shields
shields copied to clipboard
OSS Lifecycle GitLab
📋 Description
There are already OSS Lifecycle and OSS Lifecycle Branch badges for GitHub, however it would be great to have a version that is compatible with GitLab. This would look and work like the existing OSS Lifecycle badge:
🔗 Data
The data for this would be pulled from the Raw GitLab content for that repo. On GitLab this takes the form: https://gitlab.com/{USER}/{REPO}/-/raw/{BRANCH}/{FILE}?ref_type=heads
🎤 Motivation
I largely use GitLab and would like to be able to use the badge.
I'd propose that the current OSS Lifecycle badges are renamed to indicate that they are for GitHub, with the endpoint kept the same, and a new badge pulling from GitLab with the options common for GitLab badges such as the instance URL are created.
Happy to do the work to implement and get a PR ready once a direction is confirmed.
We have a number of badges that are not using any API. They just look at a file which could be anywhere but are hard-coded to assume that file is on GitHub. I would really like to gradually move these to being agnostic about where the file is hosted. So they will work for projects on GitHub, GitLab, BitBucket, Forgejo, your self-hosted instance of Gitea.. all the places. OSSLifeCycle is one of these.
I think my suggestion here would be that we migrate this so that instead of
https://img.shields.io/osslifecycle/Netflix/osstracker
the URL becomes
https://img.shields.io/osslifecycle?file=https%3A//github.com/Netflix/osstracker/blob/master/OSSMETADATA
but file can live anywhere. To maintain backwards compatibility for existing users, we can use a redirect to redirect calls to osslifecycle/{user}/{repo}/{branch} to https://img.shields.io/osslifecycle?file=https%3A//github.com/{user}/{repo}/blob/{branch}/OSSMETADATA.
I have been having a look at this, but have run into some issues. Has this been implemented for any other endpoints I can look over as an example implementation?
So far I have added a file_url query parameter, and some logic so that if it is provided we fetch that otherwise use the existing URL structure.
The issue I am finding is with creating the bare osslifecycle without path parameters. Removing the pattern aspect of the route results in an error which I believe is an assertion error for having less than one extension, crash log below:
Crash Log
[server] /Users/jizzard/Desktop/Projects/Personal/Open Source/Shields/node_modules/joi/lib/errors.js:193
[server] return new exports.ValidationError(message, details, original);
[server] ^
[server]
[server] Error [ValidationError]: Route for OssTracker {
[server] "base": "osslifecycle",
[server] "queryParamSchema": {
[server] "type": "object",
[server] "$_root": {
[server] "_types": {},
[server] "alternatives": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "any": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "array": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "boolean": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "date": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "function": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "link": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "number": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "object": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "string": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "symbol": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "binary": function (...args) {\n\n Assert(!args.length || ['alternatives', 'link', 'object'].includes(type), 'The', type, 'type does not allow arguments');\n return internals.generate(this, internals.types[type], args);\n },
[server] "allow": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "custom": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "disallow": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "equal": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "exist": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "forbidden": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "invalid": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "not": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "only": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "optional": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "options": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "prefs": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "preferences": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "required": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "strip": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "valid": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "when": function (...args) {\n\n return this.any()[method](...args);\n },
[server] "ValidationError": "[class extends Error {\n\n constructor(message, details, original) {\n\n super(message);\n this._original = original;\n this.details = details;\n }\n\n static isError(err) {\n\n return err instanceof exports.ValidationError;\n }\n}]",
[server] "version": "17.13.3",
[server] "cache": {
[server] "provision": "[provision(options) {\n\n return new internals.Cache(options);\n }]"
[server] },
[server] "assert": "[assert(value, schema, ...args /* [message], [options] */) {\n\n internals.assert(value, schema, true, args);\n }]",
[server] "attempt": "[attempt(value, schema, ...args /* [message], [options] */) {\n\n return internals.assert(value, schema, false, args);\n }]",
[server] "build": "[build(desc) {\n\n Assert(typeof Manifest.build === 'function', 'Manifest functionality disabled');\n return Manifest.build(this, desc);\n }]",
[server] "checkPreferences": "[checkPreferences(prefs) {\n\n Common.checkPreferences(prefs);\n }]",
[server] "compile": "[compile(schema, options) {\n\n return Compile.compile(this, schema, options);\n }]",
[server] "defaults": "[defaults(modifier) {\n\n Assert(typeof modifier === 'function', 'modifier must be a function');\n\n const joi = Object.assign({}, this);\n for (const type of joi._types) {\n const schema = modifier(joi[type]());\n Assert(Common.isSchema(schema), 'modifier must return a valid schema object');\n\n joi[type] = function (...args) {\n\n return internals.generate(this, schema, args);\n };\n }\n\n return joi;\n }]",
[server] "expression": "[expression(...args) {\n\n return new Template(...args);\n }]",
[server] "extend": "[extend(...extensions) {\n\n Common.verifyFlat(extensions, 'extend');\n\n Schemas = Schemas || require('./schemas');\n\n Assert(extensions.length, 'You need to provide at least one extension');\n this.assert(extensions, Schemas.extensions);\n\n const joi = Object.assign({}, this);\n joi._types = new Set(joi._types);\n\n for (let extension of extensions) {\n if (typeof extension === 'function') {\n extension = extension(joi);\n }\n\n this.assert(extension, Schemas.extension);\n\n const expanded = internals.expandExtension(extension, joi);\n for (const item of expanded) {\n Assert(joi[item.type] === undefined || joi._types.has(item.type), 'Cannot override name', item.type);\n\n const base = item.base || this.any();\n const schema = Extend.type(base, item);\n\n joi._types.add(item.type);\n joi[item.type] = function (...args) {\n\n return internals.generate(this, schema, args);\n };\n }\n }\n\n return joi;\n }]",
"isError": "[isError(err) {\n\n return e[nodemon] app crashed - waiting for file changes before starting...
I also tried making the pattern optional but get an error than the Route and OpenAPI Spec don't match, and it doesn't seem to be possible to in OpenAPI to have an optional path parameter.
Yeah sure. Why not look at Python Version from pyproject.toml service
https://github.com/badges/shields/blob/master/services/python/python-version-from-toml.service.js
https://shields.io/badges/python-version-from-pep-621-toml
It is difficult to tell exactly why you are getting the errors you are getting because I can't see your code, but my guess is it is because you're changing the route object but not updating the signature of the handle() function and/or the openApi object to match it.
Thanks @chris48s, that pointed me in the right direction! It was mostly down to having osslifecycle as the base, moving it to the pattern solved the issue as it was considered part of the extension.