SVG by default (#3717)
Make cleaner badge URLs by omitting the `.svg` extension. Closes #2674
This commit is contained in:
@@ -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) {
|
||||
|
||||
@@ -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)}`,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}`
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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' })
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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()))
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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']`)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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!
|
||||
|
||||
@@ -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">')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 />
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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=<URL>&label=<LABEL>&query=<
|
||||
/badge/dynamic/json?url=<URL>&label=<LABEL>&query=<
|
||||
<a
|
||||
href="https://jsonpath.com"
|
||||
target="_BLANK"
|
||||
@@ -257,7 +257,7 @@ export default function Usage({ baseUrl }) {
|
||||
<p>
|
||||
<StyledCode>
|
||||
{baseUrl}
|
||||
/badge/dynamic/xml.svg?url=<URL>&label=<LABEL>&query=<
|
||||
/badge/dynamic/xml?url=<URL>&label=<LABEL>&query=<
|
||||
<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=<URL>&label=<LABEL>&query=<
|
||||
/badge/dynamic/yaml?url=<URL>&label=<LABEL>&query=<
|
||||
<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>
|
||||
)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
''
|
||||
given('https://img.shields.io/badge', undefined, 'Example').expect(
|
||||
''
|
||||
)
|
||||
given(
|
||||
'https://img.shields.io/badge.svg',
|
||||
'https://img.shields.io/badge',
|
||||
'https://example.com/example',
|
||||
'Example'
|
||||
).expect(
|
||||
'[](https://example.com/example)'
|
||||
'[](https://example.com/example)'
|
||||
)
|
||||
given('https://img.shields.io/badge.svg', undefined, undefined).expect(
|
||||
''
|
||||
given('https://img.shields.io/badge', undefined, undefined).expect(
|
||||
''
|
||||
)
|
||||
})
|
||||
|
||||
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(
|
||||
'[](https://example.com/example)'
|
||||
'[](https://example.com/example)'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -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
3
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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(:.+?)?',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = class SnykVulnerabilityNpm extends SynkVulnerabilityBase {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'snyk/vulnerabilities/npm',
|
||||
pattern: ':packageName(.+)',
|
||||
pattern: ':packageName(.+?)',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ module.exports = class Sourcegraph extends BaseJsonService {
|
||||
static get route() {
|
||||
return {
|
||||
base: 'sourcegraph/rrc',
|
||||
pattern: ':repo(.*)',
|
||||
pattern: ':repo(.*?)',
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ module.exports = class StaticBadge extends BaseStaticService {
|
||||
static get route() {
|
||||
return {
|
||||
base: '',
|
||||
format: '(?::|badge/)((?:[^-]|--)*?)-?((?:[^-]|--)*)-((?:[^-]|--)+)',
|
||||
format: '(?::|badge/)((?:[^-]|--)*?)-?((?:[^-]|--)*)-((?:[^-.]|--)+)',
|
||||
capture: ['label', 'message', 'color'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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'],
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user