SVG by default (#3717)

Make cleaner badge URLs by omitting the `.svg` extension.

Closes #2674
This commit is contained in:
Paul Melnikow
2019-07-24 12:57:39 -05:00
committed by GitHub
parent e6f8c4ed65
commit cfbd2c30df
34 changed files with 169 additions and 168 deletions

View File

@@ -9,7 +9,7 @@ function badgeUrlFromPath({
path,
queryParams,
style,
format = 'svg',
format = '',
longCache = false,
}) {
const outExt = format.length ? `.${format}` : ''
@@ -30,7 +30,7 @@ function badgeUrlFromPattern({
namedParams,
queryParams,
style,
format = 'svg',
format = '',
longCache = false,
}) {
const toPath = pathToRegexp.compile(pattern, {
@@ -61,15 +61,16 @@ function staticBadgeUrl({
color = 'lightgray',
style,
namedLogo,
format = 'svg',
format = '',
}) {
const path = [label, message, color].map(encodeField).join('-')
const outQueryString = queryString.stringify({
style,
logo: namedLogo,
})
const outExt = format.length ? `.${format}` : ''
const suffix = outQueryString ? `?${outQueryString}` : ''
return `${baseUrl}/badge/${path}.${format}${suffix}`
return `${baseUrl}/badge/${path}${outExt}${suffix}`
}
function queryStringStaticBadgeUrl({
@@ -83,7 +84,7 @@ function queryStringStaticBadgeUrl({
logoColor,
logoWidth,
logoPosition,
format = 'svg',
format = '',
}) {
// schemaVersion could be a parameter if we iterate on it,
// for now it's hardcoded to the only supported version.
@@ -99,7 +100,8 @@ function queryStringStaticBadgeUrl({
logoWidth,
logoPosition,
})}`
return `${baseUrl}/static/v${schemaVersion}.${format}${suffix}`
const outExt = format.length ? `.${format}` : ''
return `${baseUrl}/static/v${schemaVersion}${outExt}${suffix}`
}
function dynamicBadgeUrl({
@@ -112,8 +114,10 @@ function dynamicBadgeUrl({
suffix,
color,
style,
format = 'svg',
format = '',
}) {
const outExt = format.length ? `.${format}` : ''
const queryParams = {
label,
url: dataUrl,
@@ -132,7 +136,7 @@ function dynamicBadgeUrl({
}
const outQueryString = queryString.stringify(queryParams)
return `${baseUrl}/badge/dynamic/${datatype}.${format}?${outQueryString}`
return `${baseUrl}/badge/dynamic/${datatype}${outExt}?${outQueryString}`
}
function rasterRedirectUrl({ rasterUrl }, badgeUrl) {

View File

@@ -18,7 +18,7 @@ describe('Badge URL generation functions', function() {
style: 'flat-square',
longCache: true,
}).expect(
'http://example.com/npm/v/gh-badges.svg?cacheSeconds=2592000&style=flat-square'
'http://example.com/npm/v/gh-badges?cacheSeconds=2592000&style=flat-square'
)
})
@@ -30,7 +30,7 @@ describe('Badge URL generation functions', function() {
style: 'flat-square',
longCache: true,
}).expect(
'http://example.com/npm/v/gh-badges.svg?cacheSeconds=2592000&style=flat-square'
'http://example.com/npm/v/gh-badges?cacheSeconds=2592000&style=flat-square'
)
})
@@ -48,7 +48,7 @@ describe('Badge URL generation functions', function() {
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect('/badge/foo-bar-blue.svg?style=flat-square')
}).expect('/badge/foo-bar-blue?style=flat-square')
given({
label: 'foo',
message: 'bar',
@@ -62,24 +62,24 @@ describe('Badge URL generation functions', function() {
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/badge/Hello%20World-%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80-%23aabbcc.svg'
'/badge/Hello%20World-%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80-%23aabbcc'
)
given({
label: '123-123',
message: 'abc-abc',
color: 'blue',
}).expect('/badge/123--123-abc--abc-blue.svg')
}).expect('/badge/123--123-abc--abc-blue')
given({
label: '123-123',
message: '',
color: 'blue',
style: 'social',
}).expect('/badge/123--123--blue.svg?style=social')
}).expect('/badge/123--123--blue?style=social')
given({
label: '',
message: 'blue',
color: 'blue',
}).expect('/badge/-blue-blue.svg')
}).expect('/badge/-blue-blue')
})
test(queryStringStaticBadgeUrl, () => {
@@ -89,9 +89,7 @@ describe('Badge URL generation functions', function() {
message: 'bar',
color: 'blue',
style: 'flat-square',
}).expect(
'/static/v1.svg?color=blue&label=foo&message=bar&style=flat-square'
)
}).expect('/static/v1?color=blue&label=foo&message=bar&style=flat-square')
given({
label: 'foo Bar',
message: 'bar Baz',
@@ -107,7 +105,7 @@ describe('Badge URL generation functions', function() {
message: 'Привет Мир',
color: '#aabbcc',
}).expect(
'/static/v1.svg?color=%23aabbcc&label=Hello%20World&message=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80'
'/static/v1?color=%23aabbcc&label=Hello%20World&message=%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80'
)
})
@@ -125,7 +123,7 @@ describe('Badge URL generation functions', function() {
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'http://img.example.com/badge/dynamic/json',
'?label=foo',
`&prefix=${encodeURIComponent(prefix)}`,
`&query=${encodeURIComponent(query)}`,
@@ -146,7 +144,7 @@ describe('Badge URL generation functions', function() {
style: 'plastic',
}).expect(
[
'http://img.example.com/badge/dynamic/json.svg',
'http://img.example.com/badge/dynamic/json',
'?color=blue',
'&label=foo',
`&query=${encodeURIComponent(query)}`,

View File

@@ -50,7 +50,7 @@ module.exports = class NonMemoryCachingBaseService extends BaseService {
)
// The final capture group is the extension.
const format = match.slice(-1)[0]
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
badgeData.format = format
const svg = makeBadge(badgeData)

View File

@@ -45,7 +45,7 @@ module.exports = class BaseStaticService extends BaseService {
)
// The final capture group is the extension.
const format = match.slice(-1)[0]
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
badgeData.format = format
if (shouldProfileMakeBadge) {

View File

@@ -438,7 +438,7 @@ class BaseService {
this
)
// The final capture group is the extension.
const format = match.slice(-1)[0]
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
sendBadge(format, badgeData)
serviceRequestCounter.inc()

View File

@@ -316,7 +316,7 @@ describe('BaseService', function() {
})
describe('ScoutCamp integration', function() {
const expectedRouteRegex = /^\/foo\/([^/]+?)\.(svg|json)$/
const expectedRouteRegex = /^\/foo\/([^/]+?)(|\.svg|\.json)$/
let mockCamp
let mockHandleRequest

View File

@@ -106,7 +106,7 @@ module.exports = function redirector(attrs) {
}
// The final capture group is the extension.
const format = match.slice(-1)[0]
const format = (match.slice(-1)[0] || '.svg').replace(/^\./, '')
const redirectUrl = `${
format === 'png' ? rasterUrl : ''
}${targetPath}.${format}${urlSuffix}`

View File

@@ -1,5 +1,6 @@
'use strict'
const escapeStringRegexp = require('escape-string-regexp')
const Joi = require('@hapi/joi')
const pathToRegexp = require('path-to-regexp')
@@ -27,15 +28,16 @@ function assertValidRoute(route, message = undefined) {
}
function prepareRoute({ base, pattern, format, capture, withPng }) {
const extensionRegex = ['svg', 'json']
.concat(withPng ? ['png'] : [])
const extensionRegex = ['', '.svg', '.json']
.concat(withPng ? ['.png'] : [])
.map(escapeStringRegexp)
.join('|')
let regex, captureNames
if (pattern === undefined) {
regex = new RegExp(`^${makeFullUrl(base, format)}\\.(${extensionRegex})$`)
regex = new RegExp(`^${makeFullUrl(base, format)}(${extensionRegex})$`)
captureNames = capture || []
} else {
const fullPattern = `${makeFullUrl(base, pattern)}.:ext(${extensionRegex})`
const fullPattern = `${makeFullUrl(base, pattern)}:ext(${extensionRegex})`
const keys = []
regex = pathToRegexp(fullPattern, keys, {
strict: true,

View File

@@ -19,13 +19,7 @@ describe('Route helpers', function() {
const regexExec = str => regex.exec(str)
test(regexExec, () => {
forCases([
given('/foo/bar.bar.bar.zip'),
given('/foo/bar/bar.svg'),
// This is a valid example with the wrong extension separator, to
// test that we only accept a `.`.
given('/foo/bar.bar.bar_svg'),
]).expect(null)
given('/foo/bar/bar.svg').expect(null)
})
const namedParams = str =>
@@ -35,25 +29,23 @@ describe('Route helpers', function() {
given('/foo/bar.bar.bar.svg'),
given('/foo/bar.bar.bar.json'),
]).expect({ namedParamA: 'bar.bar.bar' })
// This pattern catches bugs related to escaping the extension separator.
given('/foo/bar.bar.bar_svg').expect({ namedParamA: 'bar.bar.bar_svg' })
given('/foo/bar.bar.bar.zip').expect({ namedParamA: 'bar.bar.bar.zip' })
})
})
context('A `format` with a named param is declared', function() {
const { regex, captureNames } = prepareRoute({
base: 'foo',
format: '([^/]+)',
format: '([^/]+?)',
capture: ['namedParamA'],
})
const regexExec = str => regex.exec(str)
test(regexExec, () => {
forCases([
given('/foo/bar.bar.bar.zip'),
given('/foo/bar/bar.svg'),
// This is a valid example with the wrong extension separator, to
// test that we only accept a `.`.
given('/foo/bar.bar.bar_svg'),
]).expect(null)
given('/foo/bar/bar.svg').expect(null)
})
const namedParams = str =>
@@ -63,6 +55,10 @@ describe('Route helpers', function() {
given('/foo/bar.bar.bar.svg'),
given('/foo/bar.bar.bar.json'),
]).expect({ namedParamA: 'bar.bar.bar' })
// This pattern catches bugs related to escaping the extension separator.
given('/foo/bar.bar.bar_svg').expect({ namedParamA: 'bar.bar.bar_svg' })
given('/foo/bar.bar.bar.zip').expect({ namedParamA: 'bar.bar.bar.zip' })
})
})

View File

@@ -3,7 +3,6 @@
* @module
*/
const fs = require('fs')
const path = require('path')
const url = require('url')
const bytes = require('bytes')
@@ -27,11 +26,6 @@ const PrometheusMetrics = require('./prometheus-metrics')
const optionalUrl = Joi.string().uri({ scheme: ['http', 'https'] })
const requiredUrl = optionalUrl.required()
const notFound = fs.readFileSync(
path.resolve(__dirname, 'error-pages', '404.html'),
'utf-8'
)
const publicConfigSchema = Joi.object({
bind: {
port: Joi.number().port(),
@@ -189,7 +183,7 @@ class Server {
public: { rasterUrl },
} = config
camp.notfound(/\.(gif|jpg)$/, (query, match, end, request) => {
camp.route(/\.(gif|jpg)$/, (query, match, end, request) => {
const [, format] = match
makeSend('svg', request.res, end)(
makeBadge({
@@ -201,7 +195,7 @@ class Server {
})
if (!rasterUrl) {
camp.notfound(/\.png$/, (query, match, end, request) => {
camp.route(/\.png$/, (query, match, end, request) => {
makeSend('svg', request.res, end)(
makeBadge({
text: ['404', 'raster badges not available'],
@@ -212,8 +206,10 @@ class Server {
})
}
camp.notfound(/\.(svg|json)$/, (query, match, end, request) => {
const [, format] = match
camp.notfound(/(.svg|.json|)$/, (query, match, end, request) => {
const [, extension] = match
const format = (extension || '.svg').replace(/^\./, '')
makeSend(format, request.res, end)(
makeBadge({
text: ['404', 'badge not found'],
@@ -222,34 +218,6 @@ class Server {
})
)
})
camp.notfound(/.*/, (query, match, end, request) => {
end(notFound)
})
}
/**
* Iterate all the service classes defined in /services,
* load each service and register a Scoutcamp route for each service.
*/
registerServices() {
const { config, camp } = this
const { apiProvider: githubApiProvider } = this.githubConstellation
const { requestCounter } = this.metrics || {}
loadServiceClasses().forEach(serviceClass =>
serviceClass.register(
{ camp, handleRequest, githubApiProvider, requestCounter },
{
handleInternalErrors: config.public.handleInternalErrors,
cacheHeaders: config.public.cacheHeaders,
profiling: config.public.profiling,
fetchLimitBytes: bytes(config.public.fetchLimit),
rasterUrl: config.public.rasterUrl,
private: config.private,
}
)
)
}
/**
@@ -290,6 +258,30 @@ class Server {
}
}
/**
* Iterate all the service classes defined in /services,
* load each service and register a Scoutcamp route for each service.
*/
registerServices() {
const { config, camp } = this
const { apiProvider: githubApiProvider } = this.githubConstellation
const { requestCounter } = this.metrics || {}
loadServiceClasses().forEach(serviceClass =>
serviceClass.register(
{ camp, handleRequest, githubApiProvider, requestCounter },
{
handleInternalErrors: config.public.handleInternalErrors,
cacheHeaders: config.public.cacheHeaders,
profiling: config.public.profiling,
fetchLimitBytes: bytes(config.public.fetchLimit),
rasterUrl: config.public.rasterUrl,
private: config.private,
}
)
)
}
/**
* Start the HTTP server:
* Bootstrap Scoutcamp,
@@ -327,8 +319,8 @@ class Server {
suggest.setRoutes(allowedOrigin, githubApiProvider, camp)
this.registerErrorHandlers()
this.registerServices()
this.registerRedirects()
this.registerServices()
await new Promise(resolve => camp.on('listening', () => resolve()))
}

View File

@@ -55,6 +55,15 @@ describe('The server', function() {
)
})
it('should produce json badges', async function() {
const { statusCode, body, headers } = await got(
`${baseUrl}npm/v/express.json`
)
expect(statusCode).to.equal(200)
expect(headers['content-type']).to.equal('application/json')
expect(() => JSON.parse(body)).not.to.throw()
})
it('should preserve label case', async function() {
const { statusCode, body } = await got(`${baseUrl}:fRuiT-apple-green.svg`)
expect(statusCode).to.equal(200)
@@ -109,13 +118,16 @@ describe('The server', function() {
.and.to.include('badge not found')
})
it('should return the 404 html page for rando links', async function() {
it('should return the 404 badge page for rando links', async function() {
const { statusCode, body } = await got(
`${baseUrl}this/is/most/definitely/not/a/badge.js`,
{ throwHttpErrors: false }
)
expect(statusCode).to.equal(404)
expect(body).to.include('blood, toil, tears and sweat')
expect(body)
.to.satisfy(isSvg)
.and.to.include('404')
.and.to.include('badge not found')
})
it('should redirect the root as configured', async function() {
@@ -132,7 +144,8 @@ describe('The server', function() {
const { statusCode, body } = await got(`${baseUrl}npm/v/express.jpg`, {
throwHttpErrors: false,
})
expect(statusCode).to.equal(404)
// TODO It would be nice if this were 404 or 410.
expect(statusCode).to.equal(200)
expect(body)
.to.satisfy(isSvg)
.and.to.include('410')

View File

@@ -26,13 +26,13 @@ describe('Main page', function() {
expectBadgeExample(
'Discourse status',
'http://localhost:8080/badge/discourse-online-brightgreen.svg',
'/discourse/:scheme/:host/status.svg'
'http://localhost:8080/badge/discourse-online-brightgreen',
'/discourse/:scheme/:host/status'
)
})
it('Suggest badges', function() {
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
@@ -42,7 +42,7 @@ describe('Main page', function() {
})
it('Customization form is filled with suggested badge details', function() {
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
cy.contains('Suggest badges').click()
@@ -54,7 +54,7 @@ describe('Main page', function() {
})
it('Customizate suggested badge', function() {
const badgeUrl = `${backendUrl}/github/issues/badges/shields.svg`
const badgeUrl = `${backendUrl}/github/issues/badges/shields`
cy.visit('/')
cy.get(SEARCH_INPUT).type('https://github.com/badges/shields')
cy.contains('Suggest badges').click()
@@ -62,8 +62,6 @@ describe('Main page', function() {
cy.get('table input[name="color"]').type('orange')
cy.get(
`img[src='${backendUrl}/github/issues/badges/shields.svg?color=orange']`
)
cy.get(`img[src='${backendUrl}/github/issues/badges/shields?color=orange']`)
})
})

View File

@@ -22,7 +22,7 @@ module.exports = deprecatedService({
category: 'size',
route: {
base: 'imagelayers',
format: '(?:.+)',
format: '(?:.+?)',
},
label: 'imagelayers',
dateAdded: new Date('2019-xx-xx'), // Be sure to update this with today's date!

View File

@@ -33,14 +33,12 @@ describe('Common modules', function() {
describe('<Badge />', function() {
it('renders', function() {
shallow(<common.Badge src="/badge/foo-bar-blue.svg" />)
shallow(<common.Badge src="/badge/foo-bar-blue" />)
})
it('contains a link to the image', function() {
const wrapper = render(<common.Badge src="/badge/foo-bar-blue.svg" />)
expect(wrapper.html()).to.contain(
'<img alt src="/badge/foo-bar-blue.svg">'
)
const wrapper = render(<common.Badge src="/badge/foo-bar-blue" />)
expect(wrapper.html()).to.contain('<img alt src="/badge/foo-bar-blue">')
})
})

View File

@@ -36,7 +36,7 @@ export default function Customizer({
function generateBuiltBadgeUrl() {
const suffix = queryString ? `?${queryString}` : ''
return `${baseUrl || getBaseUrlFromWindowLocation()}${path}.svg${suffix}`
return `${baseUrl || getBaseUrlFromWindowLocation()}${path}${suffix}`
}
function renderLivePreview() {

View File

@@ -6,16 +6,12 @@ import '../enzyme-conf.spec'
describe('<Snippet />', function() {
it('renders', function() {
render(<Snippet snippet="http://example.com/badge.svg" />)
render(<Snippet snippet="http://example.com/badge" />)
})
it('renders with truncate and fontSize', function() {
render(
<Snippet
fontSize="14pt"
snippet="http://example.com/badge.svg"
truncate
/>
<Snippet fontSize="14pt" snippet="http://example.com/badge" truncate />
)
})
})

View File

@@ -183,13 +183,13 @@ export default function Usage({ baseUrl }) {
<p>Using dash "-" separator</p>
<p>
<Snippet snippet={`${baseUrl}/badge/<LABEL>-<MESSAGE>-<COLOR>.svg`} />
<Snippet snippet={`${baseUrl}/badge/<LABEL>-<MESSAGE>-<COLOR>`} />
</p>
<StaticBadgeEscapingRules />
<p>Using query string parameters</p>
<p>
<Snippet
snippet={`${baseUrl}/static/v1.svg?label=<LABEL>&message=<MESSAGE>&color=<COLOR>`}
snippet={`${baseUrl}/static/v1?label=<LABEL>&message=<MESSAGE>&color=<COLOR>`}
/>
</p>
@@ -229,7 +229,7 @@ export default function Usage({ baseUrl }) {
<H3>Endpoint</H3>
<p>
<Snippet snippet={`${baseUrl}/endpoint.svg?url=<URL>&style<STYLE>`} />
<Snippet snippet={`${baseUrl}/endpoint?url=<URL>&style<STYLE>`} />
</p>
<p>
@@ -243,7 +243,7 @@ export default function Usage({ baseUrl }) {
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/json.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
/badge/dynamic/json?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
href="https://jsonpath.com"
target="_BLANK"
@@ -257,7 +257,7 @@ export default function Usage({ baseUrl }) {
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/xml.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
/badge/dynamic/xml?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a href="http://xpather.com" target="_BLANK" title="XPath syntax">
//data/subdata
</a>
@@ -267,7 +267,7 @@ export default function Usage({ baseUrl }) {
<p>
<StyledCode>
{baseUrl}
/badge/dynamic/yaml.svg?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
/badge/dynamic/yaml?url=&lt;URL&gt;&amp;label=&lt;LABEL&gt;&amp;query=&lt;
<a
href="https://jsonpath.com"
target="_BLANK"
@@ -394,8 +394,19 @@ export default function Usage({ baseUrl }) {
</QueryParamTable>
<p>
We support <code>.svg</code>, <code>.json</code>, <code>.png</code> and
a few others, but use them responsibly.
We support <code>.svg</code> and <code>.json</code>. The default is
<code>.svg</code>, which can be omitted from the URL.
</p>
<p>
While we highly recommend using SVG, we also support <code>.png</code>
for use cases where SVG will not work. These requests should be made to
our raster server <code>https://raster.shields.io</code>. For example,
the raster equivalent of
<code>https://img.shields.io/v/npm/express</code> is
<code>https://raster.shields.io/v/npm/express</code>. For backward
compatibility, the badge server will redirect <code>.png</code> badges
to the raster server.
</p>
</section>
)

View File

@@ -12,7 +12,7 @@ describe('<Usage />', function() {
it('contains some of the expected text', function() {
const wrapper = shallow(<Usage baseUrl="https://example.shields.io" />)
expect(wrapper).to.contain.text('use them responsibly')
expect(wrapper).to.contain.text('For backward compatibility')
})
// This test requires Link to be mocked.

View File

@@ -11,54 +11,54 @@ import {
test(bareLink, () => {
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect('https://img.shields.io/badge.svg')
).expect('https://img.shields.io/badge')
})
test(html, () => {
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect(
'<a href="https://example.com/example"><img alt="Example" src="https://img.shields.io/badge.svg"></a>'
'<a href="https://example.com/example"><img alt="Example" src="https://img.shields.io/badge"></a>'
)
given('https://img.shields.io/badge.svg', undefined, undefined).expect(
'<img src="https://img.shields.io/badge.svg">'
given('https://img.shields.io/badge', undefined, undefined).expect(
'<img src="https://img.shields.io/badge">'
)
})
test(markdown, () => {
given('https://img.shields.io/badge.svg', undefined, 'Example').expect(
'![Example](https://img.shields.io/badge.svg)'
given('https://img.shields.io/badge', undefined, 'Example').expect(
'![Example](https://img.shields.io/badge)'
)
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect(
'[![Example](https://img.shields.io/badge.svg)](https://example.com/example)'
'[![Example](https://img.shields.io/badge)](https://example.com/example)'
)
given('https://img.shields.io/badge.svg', undefined, undefined).expect(
'![](https://img.shields.io/badge.svg)'
given('https://img.shields.io/badge', undefined, undefined).expect(
'![](https://img.shields.io/badge)'
)
})
test(reStructuredText, () => {
given('https://img.shields.io/badge.svg', undefined, undefined).expect(
'.. image:: https://img.shields.io/badge.svg'
given('https://img.shields.io/badge', undefined, undefined).expect(
'.. image:: https://img.shields.io/badge'
)
given('https://img.shields.io/badge.svg', undefined, 'Example').expect(
'.. image:: https://img.shields.io/badge.svg :alt: Example'
given('https://img.shields.io/badge', undefined, 'Example').expect(
'.. image:: https://img.shields.io/badge :alt: Example'
)
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect(
'.. image:: https://img.shields.io/badge.svg :alt: Example :target: https://example.com/example'
'.. image:: https://img.shields.io/badge :alt: Example :target: https://example.com/example'
)
})
@@ -70,33 +70,33 @@ test(renderAsciiDocAttributes, () => {
})
test(asciiDoc, () => {
given('https://img.shields.io/badge.svg', undefined, undefined).expect(
'image:https://img.shields.io/badge.svg[]'
given('https://img.shields.io/badge', undefined, undefined).expect(
'image:https://img.shields.io/badge[]'
)
given('https://img.shields.io/badge.svg', undefined, 'Example').expect(
'image:https://img.shields.io/badge.svg[Example]'
given('https://img.shields.io/badge', undefined, 'Example').expect(
'image:https://img.shields.io/badge[Example]'
)
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
undefined,
'Example, with comma'
).expect('image:https://img.shields.io/badge.svg["Example, with comma"]')
).expect('image:https://img.shields.io/badge["Example, with comma"]')
given(
'https://img.shields.io/badge.svg',
'https://img.shields.io/badge',
'https://example.com/example',
'Example'
).expect(
'image:https://img.shields.io/badge.svg["Example",link="https://example.com/example"]'
'image:https://img.shields.io/badge["Example",link="https://example.com/example"]'
)
})
test(generateMarkup, () => {
given({
badgeUrl: 'https://img.shields.io/badge.svg',
badgeUrl: 'https://img.shields.io/badge',
link: 'https://example.com/example',
title: 'Example',
markupFormat: 'markdown',
}).expect(
'[![Example](https://img.shields.io/badge.svg)](https://example.com/example)'
'[![Example](https://img.shields.io/badge)](https://example.com/example)'
)
})

View File

@@ -88,7 +88,7 @@ const EndpointPage = () => (
<Meta />
<Header />
<H3>Endpoint</H3>
<Snippet snippet={`${baseUrl}/endpoint.svg?url=...&style=...`} />
<Snippet snippet={`${baseUrl}/endpoint?url=...&style=...`} />
<p>Endpoint response:</p>
<JsonExample
data={{

3
package-lock.json generated
View File

@@ -8965,8 +8965,7 @@
"escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"dev": true
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="
},
"escodegen": {
"version": "1.9.0",

View File

@@ -34,6 +34,7 @@
"decamelize": "^3.2.0",
"dotenv": "^8.0.0",
"emojic": "^1.1.15",
"escape-string-regexp": "^2.0.0",
"fast-xml-parser": "^3.12.16",
"fsos": "^1.1.6",
"gh-badges": "file:gh-badges",
@@ -171,7 +172,6 @@
"danger-plugin-no-test-shortcuts": "^2.0.0",
"enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0",
"escape-string-regexp": "^2.0.0",
"eslint": "^5.16.0",
"eslint-config-prettier": "^6.0.0",
"eslint-config-standard": "^12.0.0",

View File

@@ -6,7 +6,7 @@ module.exports = deprecatedService({
category: 'issue-tracking',
route: {
base: 'issuestats',
format: '(?:[^/]+)(?:/long)?/(?:[^/]+)/(?:.+)',
format: '(?:.*?)',
},
label: 'issue stats',
dateAdded: new Date('2018-09-01'),

View File

@@ -46,7 +46,7 @@ module.exports = class Nexus extends BaseJsonService {
pattern:
// Do not base new services on this route pattern.
// See https://github.com/badges/shields/issues/3714
':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+):queryOpt(:.+)?',
':repo(r|s|[^/]+)/:scheme(http|https)/:hostAndPath+/:groupId/:artifactId([^/:]+?):queryOpt(:.+?)?',
}
}

View File

@@ -156,7 +156,7 @@ function createServiceFamily({
static get route() {
return buildRoute({ serviceBaseUrl, withTenant, withFeed })
.push('(v|vpre)', 'which')
.push('(.*)', 'packageName')
.push('(.+?)', 'packageName')
.toObject()
}
@@ -207,7 +207,7 @@ function createServiceFamily({
static get route() {
return buildRoute({ serviceBaseUrl, withTenant, withFeed })
.push('dt')
.push('(.*)', 'packageName')
.push('(.+?)', 'packageName')
.toObject()
}

View File

@@ -7,7 +7,7 @@ module.exports = class SnykVulnerabilityNpm extends SynkVulnerabilityBase {
static get route() {
return {
base: 'snyk/vulnerabilities/npm',
pattern: ':packageName(.+)',
pattern: ':packageName(.+?)',
}
}

View File

@@ -18,7 +18,7 @@ module.exports = class Sourcegraph extends BaseJsonService {
static get route() {
return {
base: 'sourcegraph/rrc',
pattern: ':repo(.*)',
pattern: ':repo(.*?)',
}
}

View File

@@ -11,7 +11,7 @@ module.exports = class StaticBadge extends BaseStaticService {
static get route() {
return {
base: '',
format: '(?::|badge/)((?:[^-]|--)*?)-?((?:[^-]|--)*)-((?:[^-]|--)+)',
format: '(?::|badge/)((?:[^-]|--)*?)-?((?:[^-]|--)*)-((?:[^-.]|--)+)',
capture: ['label', 'message', 'color'],
}
}

View File

@@ -24,7 +24,7 @@ module.exports = class TeamCityBuild extends TeamCityBase {
base: 'teamcity',
// Do not base new services on this route pattern.
// See https://github.com/badges/shields/issues/3714
format: '(?:codebetter|(http|https)/(.+)/(s|e))/([^/]+)',
format: '(?:codebetter|(http|https)/(.+)/(s|e))/([^/]+?)',
capture: ['protocol', 'hostAndPath', 'verbosity', 'buildId'],
}
}

View File

@@ -26,7 +26,7 @@ module.exports = class TeamCityCoverage extends TeamCityBase {
// Do not base new services on this route pattern.
// See https://github.com/badges/shields/issues/3714
base: 'teamcity/coverage',
format: '(?:(http|https)/(.+)/)?([^/]+)',
format: '(?:(http|https)/(.+)/)?([^/]+?)',
capture: ['protocol', 'hostAndPath', 'buildId'],
}
}

View File

@@ -18,7 +18,7 @@ module.exports = class TravisBuild extends BaseSvgScrapingService {
static get route() {
return {
base: 'travis',
format: '(?:(com)/)?(?!php-v)([^/]+/[^/]+)(?:/(.+))?',
format: '(?:(com)/)?(?!php-v)([^/]+/[^/]+?)(?:/(.+?))?',
capture: ['comDomain', 'userRepo', 'branch'],
}
}

View File

@@ -2,13 +2,7 @@
const Joi = require('@hapi/joi')
const { isBuildStatus } = require('../build-status')
const { ServiceTester } = require('../tester')
const t = (module.exports = new ServiceTester({
id: 'travis-build',
title: 'Travis CI',
pathPrefix: '/travis',
}))
const t = (module.exports = require('../tester').createServiceTester())
// Travis (.org) CI

View File

@@ -56,7 +56,7 @@ module.exports = redirector({
route: {
base: '',
format:
'website-(([^-/]|--|//)+)-(([^-/]|--|//)+)(-(([^-/]|--|//)+)-(([^-/]|--|//)+))?/([^/]+)/(.+)',
'website-(([^-/]|--|//)+)-(([^-/]|--|//)+)(-(([^-/]|--|//)+)-(([^-/]|--|//)+))?/([^/]+)/(.+?)',
capture: [
// Some of these could be made into non-capturing groups so these unused
// params would not need to be declared.

View File

@@ -23,7 +23,7 @@ module.exports = class Wercker extends BaseJsonService {
return {
base: 'wercker',
format:
'(?:(?:ci/)([a-fA-F0-9]{24})|(?:build|ci)/([^/]+/[^/]+))(?:/(.+))?',
'(?:(?:ci/)([a-fA-F0-9]{24})|(?:build|ci)/([^/]+/[^/]+?))(?:/(.+?))?',
capture: ['projectId', 'applicationName', 'branch'],
}
}