📦 version 3 (#4756)

* Validate input to BadgeFactory.create() (#3875)

* validate input to create()

* remove deprecated properties (#3881)

* remove BadgeFactory class (#3884)

* Template literal templates (#4459)

- Remove use of the doT template library and move to generating SVG output using javascript template literals.
- Drop SVGO and mostly manually implement the optimisations.
- Add a bunch more tests

Co-authored-by: Paul Melnikow <github@paulmelnikow.com>

* drop raster support in package CLI (#4523)

* drop raster support in package CLI
* update docs

* rename gh-badges package to badge-maker

* rename gh-badges dir to badge-maker

* update relative imports and other refs to in parent dir

'gh-badges' --> 'badge-maker'

* update snyk service tests

This change is only tangentially related

We've used the shields repo as an example for these tests so
moving files around in our repo has a knock-on effect on them

* add missing type hints to dev style page

* write the changelog/migration guide for v3

* use extension in README CLI example

* update CLI help

whoops - missed this in #4523

* bump version

* update for self-hosting users

* README updates

* drop .format param from CLI, always output SVG

* Change text[] to label and message, Remove JSON output

- Change text[] to label and message
- Fix message only badge
- Remove JSON output format
- Update the docs

* update package-lock

* rename 'template' to 'style'

* handle invalid styles in coalesceBadge

* ensure makeBadge is passed a string for template in coalesceBadge()

issue #4925

* fix (logo/no label text/label color specified) case

issue #4926

* add example of (logo/no label text/label color specified) to style debug page

* update type defs

* padding fix for FTB style

Co-authored-by: Paul Melnikow <github@paulmelnikow.com>
This commit is contained in:
chris48s
2020-04-23 20:05:48 +01:00
committed by GitHub
parent b64987d2dd
commit 3ba05cb184
47 changed files with 1941 additions and 919 deletions

View File

@@ -5,8 +5,8 @@ RUN mkdir /usr/src/app/private
WORKDIR /usr/src/app
COPY package.json package-lock.json /usr/src/app/
# Without the gh-badges package.json and CLI script in place, `npm ci` will fail.
COPY gh-badges /usr/src/app/gh-badges/
# Without the badge-maker package.json and CLI script in place, `npm ci` will fail.
COPY badge-maker /usr/src/app/badge-maker/
# We need dev deps to build the front end. We don't need Cypress, though.
RUN NODE_ENV=development CYPRESS_INSTALL_BINARY=0 npm ci

View File

@@ -43,16 +43,16 @@ Every month it serves over 470 million images.
This repo hosts:
- The [Shields.io][shields.io] frontend and server code
- An [NPM library for generating badges][gh-badges]
- [documentation][gh-badges-docs]
- [changelog][gh-badges-changelog]
- An [NPM library for generating badges][badge-maker]
- [documentation][badge-maker-docs]
- [changelog][badge-maker-changelog]
- The [badge design specification][badge-spec]
[shields.io]: https://shields.io/
[gh-badges]: https://www.npmjs.com/package/gh-badges
[badge-maker]: https://www.npmjs.com/package/badge-maker
[badge-spec]: https://github.com/badges/shields/tree/master/spec
[gh-badges-docs]: https://github.com/badges/shields/tree/master/gh-badges/README.md
[gh-badges-changelog]: https://github.com/badges/shields/tree/master/gh-badges/CHANGELOG.md
[badge-maker-docs]: https://github.com/badges/shields/tree/master/badge-maker/README.md
[badge-maker-changelog]: https://github.com/badges/shields/tree/master/badge-maker/CHANGELOG.md
## Examples

View File

@@ -1,19 +1,127 @@
exports['The badge generator SVG should always produce the same SVG (unless we have changed something!) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h45v20H0z"/><path fill="#4c1" d="M45 0h45v20H45z"/><path fill="url(#b)" d="M0 0h90v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"> <text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g> </svg>
exports['The badge generator SVG should match snapshot 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#555"/><rect x="45" width="45" height="20" fill="#4c1"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge shields GitHub logo custom color (whitesmoke) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="github"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge shields GitHub logo default color (#333333) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="github"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="107" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="62" height="20" fill="#0f0"/><rect x="62" width="45" height="20" fill="#b3e"/><rect width="107" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="405" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="835" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge simple-icons javascript logo custom color (rgba(46,204,113,0.8)) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="javascript"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="45" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#b3e"/><rect x="0" width="45" height="20" fill="#b3e"/><rect width="45" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="225" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="225" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator badges with logos should always produce the same badge simple-icons javascript logo default color (#F7DF1E) 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="a"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#a)"><path fill="#555" d="M0 0h54v20H0z"/><path fill="#4c1" d="M54 0h59v20H54z"/><path fill="url(#b)" d="M0 0h113v20H0z"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="javascript"/> <text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g> </svg>
exports['The badge generator "flat" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="63" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#555"/><rect x="0" width="63" height="20" fill="#b3e"/><rect width="63" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="405" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="69" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="24" height="20" fill="#0f0"/><rect x="24" width="45" height="20" fill="#b3e"/><rect width="69" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="455" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="90" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/><rect width="90" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="NaN" height="20" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="undefined" height="20" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><g shape-rendering="crispEdges"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="20"><g shape-rendering="crispEdges"><rect width="62" height="20" fill="#0f0"/><rect x="62" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="20"><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#b3e"/><rect x="0" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="225" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="20"><g shape-rendering="crispEdges"><rect width="0" height="20" fill="#555"/><rect x="0" width="63" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="20"><g shape-rendering="crispEdges"><rect width="24" height="20" fill="#0f0"/><rect x="24" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="140" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "flat-square" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="20"><g shape-rendering="crispEdges"><rect width="45" height="20" fill="#0f0"/><rect x="45" width="45" height="20" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="140" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="NaN" height="20" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="undefined" height="20" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="18" fill="#0f0"/><rect x="45" width="45" height="18" fill="#b3e"/><rect width="90" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="107" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="107" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="62" height="18" fill="#0f0"/><rect x="62" width="45" height="18" fill="#b3e"/><rect width="107" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="405" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="835" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="835" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="45" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="45" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="18" fill="#b3e"/><rect x="0" width="45" height="18" fill="#b3e"/><rect width="45" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="225" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="225" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="63" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="63" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="18" fill="#555"/><rect x="0" width="63" height="18" fill="#b3e"/><rect width="63" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="405" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="405" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="69" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="69" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="24" height="18" fill="#0f0"/><rect x="24" width="45" height="18" fill="#b3e"/><rect width="69" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="2" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="455" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="455" y="130" transform="scale(.1)" textLength="350">grown</text></g></svg>
`
exports['The badge generator "plastic" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="90" height="18"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#fff" stop-opacity=".7"/><stop offset=".1" stop-color="#aaa" stop-opacity=".1"/><stop offset=".9" stop-color="#000" stop-opacity=".3"/><stop offset="1" stop-color="#000" stop-opacity=".5"/></linearGradient><clipPath id="r"><rect width="90" height="18" rx="4" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="45" height="18" fill="#0f0"/><rect x="45" width="45" height="18" fill="#b3e"/><rect width="90" height="18" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><text x="235" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">cactus</text><text x="235" y="130" transform="scale(.1)" textLength="350">cactus</text><text x="665" y="140" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="350">grown</text><text x="665" y="130" transform="scale(.1)" textLength="350">grown</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="NaN" height="18" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="undefined" height="18" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="147" height="28"><g shape-rendering="crispEdges"><rect width="74" height="28" fill="#0f0"/><rect x="74" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><text x="370" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1105" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="164" height="28"><g shape-rendering="crispEdges"><rect width="91" height="28" fill="#0f0"/><rect x="91" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="540" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1275" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="28"><g shape-rendering="crispEdges"><rect width="0" height="28" fill="#b3e"/><rect x="0" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><text x="365" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="91" height="28"><g shape-rendering="crispEdges"><rect width="0" height="28" fill="#555"/><rect x="0" width="91" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="545" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="105" height="28"><g shape-rendering="crispEdges"><rect width="32" height="28" fill="#0f0"/><rect x="32" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><image x="9" y="7" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="230" y="175" transform="scale(.1)" textLength="-60"></text><text x="685" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g></svg>
`
exports['The badge generator "for-the-badge" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="147" height="28"><g shape-rendering="crispEdges"><rect width="74" height="28" fill="#0f0"/><rect x="74" width="73" height="28" fill="#b3e"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100"><text x="370" y="175" transform="scale(.1)" textLength="500">CACTUS</text><text x="1105" y="175" font-weight="bold" transform="scale(.1)" textLength="490">GROWN</text></g><a target="_blank" xlink:href="https://www.google.co.uk/"><rect width="NaN" height="28" fill="rgba(0,0,0,0)"/></a><a target="_blank" xlink:href="https://shields.io/"><rect width="undefined" height="28" fill="rgba(0,0,0,0)"/></a></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="47" height="19" rx="2"/><rect x="53.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="53" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M53.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="235" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="235" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="735" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="735" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="47" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="112" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="64" height="19" rx="2"/><rect x="70.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="70" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M70.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="405" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="405" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="905" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="905" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="64" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, no logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="59" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="11" height="19" rx="2"/><rect x="17.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="17" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M17.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="55" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="55" y="140" transform="scale(.1)" textLength="10"></text><text x="375" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="375" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="11" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="25" height="19" rx="2"/><rect x="31.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="31" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M31.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="195" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="195" y="140" transform="scale(.1)" textLength="10"></text><text x="515" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="515" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="25" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message only, with logo and labelColor 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="73" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="25" height="19" rx="2"/><rect x="31.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="31" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M31.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="195" y="150" fill="#fff" transform="scale(.1)" textLength="10"></text><text x="195" y="140" transform="scale(.1)" textLength="10"></text><text x="515" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><text id="rlink" x="515" y="140" transform="scale(.1)" textLength="330">grown</text></g><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="25" height="19" rx="2" /></svg>
`
exports['The badge generator "social" template badge generation should match snapshots: message/label, with links 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="95" height="20"><style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/><stop offset="1" stop-opacity=".1"/></linearGradient><linearGradient id="b" x2="0" y2="100%"><stop offset="0" stop-color="#ccc" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><g stroke="#d5d5d5"><rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="47" height="19" rx="2"/><rect x="53.5" y="0.5" width="41" height="19" rx="2" fill="#fafafa"/><rect x="53" y="7.5" width="0.5" height="5" stroke="#fafafa"/><path d="M53.5 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/></g><g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px"><text x="235" y="150" fill="#fff" transform="scale(.1)" textLength="370">Cactus</text><text x="235" y="140" transform="scale(.1)" textLength="370">Cactus</text><text x="735" y="150" fill="#fff" transform="scale(.1)" textLength="330">grown</text><a target="_blank" xlink:href="https://www.google.co.uk/"><text id="rlink" x="735" y="140" transform="scale(.1)" textLength="330">grown</text></a></g><a target="_blank" xlink:href="https://shields.io/"><rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="47" height="19" rx="2" /></a></svg>
`
exports['The badge generator badges with logos should always produce the same badge badge with logo 1'] = `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="113" height="20"><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="113" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="54" height="20" fill="#555"/><rect x="54" width="59" height="20" fill="#4c1"/><rect width="113" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110"><image x="5" y="3" width="14" height="14" xlink:href="data:image/svg+xml;base64,PHN2ZyB4bWxu"/><text x="365" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="270">label</text><text x="365" y="140" transform="scale(.1)" textLength="270">label</text><text x="825" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="490">message</text><text x="825" y="140" transform="scale(.1)" textLength="490">message</text></g></svg>
`

View File

@@ -1,6 +1,67 @@
# Changelog
## 2.2.1
## 3.0.0
### Breaking Changes
- Package name has changed to `badge-maker` and moved to https://www.npmjs.com/package/badge-maker
- `BadgeFactory` class is removed and replaced by `makeBadge()` function.
- Deprecated parameters have been removed. In version 2.2.0 the `colorA`, `colorB` and `colorscheme` params were deprecated. In version 3.0.0 these have been removed.
- Only SVG output format is now provided. JSON format has been dropped and the `format` key has been removed.
- The `text` array has been replaced by `label` and `message` keys.
- The `template` key has been renamed `style`.
To upgrade from v2.1.1, change your code from:
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const svg = bf.create({
text: ['build', 'passed'],
format: 'svg',
template: 'flat-square',
})
```
to:
```js
const { makeBadge } = require('badge-maker')
const svg = makeBadge({
label: 'build',
message: 'passed',
style: 'flat-square',
})
```
- `ValidationError` had been added and inputs are now validated. In previous releases, invalid inputs would be discarded and replaced with defaults. For example, in 2.2.1
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const svg = bf.create({
text: ['build', 'passed'],
template: 'some invalid value',
})
```
would generate an SVG badge. In version >=3
```js
const { makeBadge } = require('badge-maker')
const svg = makeBadge({
label: 'build',
message: 'passed',
style: 'some invalid value',
})
```
will throw a `ValidationError`.
- Raster support has been removed from the CLI. It will now only output SVG. On the console, the output of `badge` can be piped to a utility like [imagemagick](https://imagemagick.org/script/command-line-processing.php). If you were previously using
```sh
badge build passed :green .gif
```
this could be replaced by
```sh
badge build passed :green | magick svg:- gif:-
```
### Security
- Removed dependency on doT library which has known vulnerabilities.
## 2.2.1 - 2019-05-30
### Fixes

View File

@@ -1,12 +1,12 @@
# gh-badges
# badge-maker
[![npm version](https://img.shields.io/npm/v/gh-badges.svg)](https://npmjs.org/package/gh-badges)
[![npm license](https://img.shields.io/npm/l/gh-badges.svg)](https://npmjs.org/package/gh-badges)
[![npm version](https://img.shields.io/npm/v/badge-maker.svg)](https://npmjs.org/package/badge-maker)
[![npm license](https://img.shields.io/npm/l/badge-maker.svg)](https://npmjs.org/package/badge-maker)
## Installation
```sh
npm install gh-badges
npm install badge-maker
```
## Usage
@@ -14,29 +14,34 @@ npm install gh-badges
### On the console
```sh
npm install -g gh-badges
badge build passed :green .png > mybadge.png
npm install -g badge-maker
badge build passed :green > mybadge.svg
```
### As a library
```js
const { BadgeFactory } = require('gh-badges')
const bf = new BadgeFactory()
const { makeBadge, ValidationError } = require('badge-maker')
const format = {
text: ['build', 'passed'],
label: 'build',
message: 'passed',
color: 'green',
template: 'flat',
}
const svg = bf.create(format)
const svg = makeBadge(format)
console.log(svg) // <svg...
try {
makeBadge({})
} catch (e) {
console.log(e) // ValidationError: Field `message` is required
}
```
### Node version support
The latest version of gh-badges supports all currently maintained Node
The latest version of badge-maker supports all currently maintained Node
versions. See the [Node Release Schedule][].
[node release schedule]: https://github.com/nodejs/Release#release-schedule
@@ -47,28 +52,17 @@ The format is the following:
```js
{
text: [ 'build', 'passed' ], // Textual information shown, in order
label: 'build', // (Optional) Badge label
message: 'passed', // (Required) Badge message
labelColor: '#555', // (Optional) Label color
color: '#4c1', // (Optional) Message color
format: 'svg', // Also supports json
color: '#4c1',
labelColor: '#555',
// See templates/ for a list of available templates.
// (Optional) One of: 'plastic', 'flat', 'flat-square', 'for-the-badge' or 'social'
// Each offers a different visual design.
template: 'flat',
// Deprecated attributes:
colorscheme: 'green', // Now an alias for `color`.
colorB: '#4c1', // Now an alias for `color`.
colorA: '#555', // Now an alias for `labelColor`.
style: 'flat',
}
```
### See also
- [templates/](./templates) for the `template` option
## Colors
There are three ways to specify `color` and `labelColor`:
@@ -126,3 +120,12 @@ There are three ways to specify `color` and `labelColor`:
[lightslategray]: https://img.shields.io/badge/lightslategray-lightslategray.svg
[css color]: https://developer.mozilla.org/en-US/docs/Web/CSS/color_value
[css/svg color]: http://www.w3.org/TR/SVG/types.html#DataTypeColor
## Raster Formats
Conversion to raster formats is no longer directly supported. In javascript
code, SVG badges can be converted to raster formats using a library like
[gm](https://www.npmjs.com/package/gm). On the console, the output of `badge`
can be piped to a utility like
[imagemagick](https://imagemagick.org/script/command-line-processing.php)
e.g: `badge build passed :green | magick svg:- gif:-`.

View File

@@ -2,23 +2,18 @@
'use strict'
const makeBadge = require('./make-badge')
const svg2img = require('./svg-to-img')
const { namedColors } = require('./color')
const { makeBadge } = require('./index')
if (process.argv.length < 4) {
console.log('Usage: badge subject status [:color] [.output] [@style]')
console.log(
'Or: badge subject status color [labelColor] [.output] [@style]'
)
console.log('Usage: badge label message [:color] [@style]')
console.log('Or: badge label message color [labelColor] [@style]')
console.log()
console.log(' color, labelColor:')
console.log(` one of ${Object.keys(namedColors).join(', ')}.`)
console.log(' #xxx (three hex digits)')
console.log(' #xxxxxx (six hex digits)')
console.log(' color (CSS color)')
console.log(' output:')
console.log(' svg, png, jpg, or gif')
console.log()
console.log('Eg: badge cactus grown :green @flat')
console.log()
@@ -26,14 +21,8 @@ if (process.argv.length < 4) {
}
// Find a format specifier.
let format = 'svg'
let style = ''
for (let i = 4; i < process.argv.length; i++) {
if (process.argv[i][0] === '.') {
format = process.argv[i].slice(1)
process.argv.splice(i, 1)
continue
}
if (process.argv[i][0] === '@') {
style = process.argv[i].slice(1)
process.argv.splice(i, 1)
@@ -41,14 +30,14 @@ for (let i = 4; i < process.argv.length; i++) {
}
}
const subject = process.argv[2]
const status = process.argv[3]
const label = process.argv[2]
const message = process.argv[3]
let color = process.argv[4] || ':green'
const colorA = process.argv[5]
const labelColor = process.argv[5]
const badgeData = { text: [subject, status], format }
const badgeData = { label, message }
if (style) {
badgeData.template = style
badgeData.style = style
}
if (color[0] === ':') {
@@ -58,28 +47,17 @@ if (color[0] === ':') {
console.error('Invalid color scheme.')
process.exit(1)
}
badgeData.colorscheme = color
badgeData.color = color
} else {
badgeData.colorB = color
if (colorA) {
badgeData.colorA = colorA
badgeData.color = color
if (labelColor) {
badgeData.labelColor = labelColor
}
}
async function main() {
const svg = makeBadge(badgeData)
if (/png|jpg|gif/.test(format)) {
const data = await svg2img(svg, format)
process.stdout.write(data)
} else {
console.log(svg)
}
}
;(async () => {
;(() => {
try {
await main()
console.log(makeBadge(badgeData))
} catch (e) {
console.error(e)
process.exit(1)

View File

@@ -1,7 +1,6 @@
'use strict'
const path = require('path')
const isPng = require('is-png')
const isSvg = require('is-svg')
const { spawn } = require('child-process-promise')
const { expect, use } = require('chai')
@@ -39,19 +38,4 @@ describe('The CLI', function() {
.to.satisfy(isSvg)
.and.to.include('#abcdef')
})
it('should produce PNG badges', async function() {
const child = runCli(['cactus', 'grown', '.png'])
// The buffering done by `child-process-promise` doesn't seem correctly to
// handle binary data.
let chunk
child.childProcess.stdout.once('data', data => {
chunk = data
})
await child
expect(chunk).to.satisfy(isPng)
})
})

View File

@@ -0,0 +1,611 @@
'use strict'
const anafanafo = require('anafanafo')
const fontFamily = 'font-family="DejaVu Sans,Verdana,Geneva,sans-serif"'
const socialFontFamily =
'font-family="Helvetica Neue,Helvetica,Arial,sans-serif"'
function capitalize(s) {
return `${s.charAt(0).toUpperCase()}${s.slice(1)}`
}
function escapeXml(s) {
if (s === undefined || typeof s !== 'string') {
return undefined
} else {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
}
function roundUpToOdd(val) {
// Increase chances of pixel grid alignment.
return val % 2 === 0 ? val + 1 : val
}
function preferredWidthOf(str) {
return roundUpToOdd((anafanafo(str) / 10) | 0)
}
function computeWidths({ label, message }) {
return {
labelWidth: preferredWidthOf(label),
messageWidth: preferredWidthOf(message),
}
}
function renderLogo({
logo,
badgeHeight,
horizPadding,
logoWidth = 14,
logoPadding = 0,
}) {
if (!logo) {
return {
hasLogo: false,
totalLogoWidth: 0,
renderedLogo: '',
}
}
const y = (badgeHeight - logoWidth) / 2
const x = horizPadding
return {
hasLogo: true,
totalLogoWidth: logoWidth + logoPadding,
renderedLogo: `<image x="${x}" y="${y}" width="${logoWidth}" height="14" xlink:href="${escapeXml(
logo
)}"/>`,
}
}
function renderText({
leftMargin,
horizPadding = 0,
content,
verticalMargin = 0,
shadow = false,
}) {
if (!content.length) {
return { renderedText: '', width: 0 }
}
const textLength = preferredWidthOf(content)
const escapedContent = escapeXml(content)
const shadowMargin = 150 + verticalMargin
const textMargin = 140 + verticalMargin
const outTextLength = 10 * textLength
const x = 10 * (leftMargin + 0.5 * textLength + horizPadding)
let renderedText = ''
if (shadow) {
renderedText = `<text x="${x}" y="${shadowMargin}" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="${outTextLength}">${escapedContent}</text>`
}
renderedText += `<text x="${x}" y="${textMargin}" transform="scale(.1)" textLength="${outTextLength}">${escapedContent}</text>`
return {
renderedText,
width: textLength,
}
}
function renderLinks({
links: [leftLink, rightLink] = [],
labelWidth,
messageWidth,
height,
}) {
leftLink = escapeXml(leftLink)
rightLink = escapeXml(rightLink)
const hasLeftLink = leftLink && leftLink.length
const hasRightLink = rightLink && rightLink.length
const leftLinkWidth = hasRightLink ? labelWidth : labelWidth + messageWidth
function render({ link, width }) {
return `<a target="_blank" xlink:href="${link}"><rect width="${width}" height="${height}" fill="rgba(0,0,0,0)"/></a>`
}
return (
(hasRightLink
? render({ link: rightLink, width: labelWidth + messageWidth })
: '') +
(hasLeftLink ? render({ link: leftLink, width: leftLinkWidth }) : '')
)
}
function renderBadge({ links, leftWidth, rightWidth, height }, main) {
const width = leftWidth + rightWidth
return `
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="${width}" height="${height}">
${main}
${renderLinks({ links, leftWidth, rightWidth, height })}
</svg>`
}
function stripXmlWhitespace(xml) {
return xml
.replace(/>\s+/g, '>')
.replace(/<\s+/g, '<')
.trim()
}
class Badge {
static get fontFamily() {
throw new Error('Not implemented')
}
static get height() {
throw new Error('Not implemented')
}
static get verticalMargin() {
throw new Error('Not implemented')
}
static get shadow() {
throw new Error('Not implemented')
}
constructor({
label,
message,
links,
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor,
}) {
const horizPadding = 5
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: this.constructor.height,
horizPadding,
logoWidth,
logoPadding,
})
const hasLabel = label.length || labelColor
if (labelColor == null) {
labelColor = '#555'
}
labelColor = hasLabel || hasLogo ? labelColor : color
labelColor = escapeXml(labelColor)
color = escapeXml(color)
const labelMargin = totalLogoWidth + 1
const { renderedText: renderedLabel, width: labelWidth } = renderText({
leftMargin: labelMargin,
horizPadding,
content: label,
verticalMargin: this.constructor.verticalMargin,
shadow: this.constructor.shadow,
})
const leftWidth = hasLabel
? labelWidth + 2 * horizPadding + totalLogoWidth
: 0
let messageMargin = leftWidth - (message.length ? 1 : 0)
if (!hasLabel) {
if (hasLogo) {
messageMargin = messageMargin + totalLogoWidth + horizPadding
} else {
messageMargin = messageMargin + 1
}
}
const { renderedText: renderedMessage, width: messageWidth } = renderText({
leftMargin: messageMargin,
horizPadding,
content: message,
verticalMargin: this.constructor.verticalMargin,
shadow: this.constructor.shadow,
})
let rightWidth = messageWidth + 2 * horizPadding
if (hasLogo && !hasLabel) {
rightWidth += totalLogoWidth + horizPadding - 1
}
const width = leftWidth + rightWidth
this.links = links
this.leftWidth = leftWidth
this.rightWidth = rightWidth
this.width = width
this.labelColor = labelColor
this.color = color
this.renderedLogo = renderedLogo
this.renderedLabel = renderedLabel
this.renderedMessage = renderedMessage
}
render() {
throw new Error('Not implemented')
}
}
class Plastic extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 18
}
static get verticalMargin() {
return -10
}
static get shadow() {
return true
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
<stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
<stop offset="1" stop-color="#000" stop-opacity=".5"/>
</linearGradient>
<clipPath id="r">
<rect width="${this.width}" height="${this.constructor.height}" rx="4" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
class Flat extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 20
}
static get verticalMargin() {
return 0
}
static get shadow() {
return true
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<linearGradient id="s" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="r">
<rect width="${this.width}" height="${this.constructor.height}" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#r)">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
<rect width="${this.width}" height="${this.constructor.height}" fill="url(#s)"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
class FlatSquare extends Badge {
static get fontFamily() {
return fontFamily
}
static get height() {
return 20
}
static get verticalMargin() {
return 0
}
static get shadow() {
return false
}
render() {
return renderBadge(
{
links: this.links,
leftWidth: this.leftWidth,
rightWidth: this.rightWidth,
height: this.constructor.height,
},
`
<g shape-rendering="crispEdges">
<rect width="${this.leftWidth}" height="${this.constructor.height}" fill="${this.labelColor}"/>
<rect x="${this.leftWidth}" width="${this.rightWidth}" height="${this.constructor.height}" fill="${this.color}"/>
</g>
<g fill="#fff" text-anchor="middle" ${this.constructor.fontFamily} font-size="110">
${this.renderedLogo}
${this.renderedLabel}
${this.renderedMessage}
</g>`
)
}
}
function plastic(params) {
const badge = new Plastic(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function flat(params) {
const badge = new Flat(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function flatSquare(params) {
const badge = new FlatSquare(params)
if (params.minify) {
return stripXmlWhitespace(badge.render())
}
return badge.render()
}
function social({
label,
message,
links = [],
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor = '#555',
minify,
}) {
// Social label is styled with a leading capital. Convert to caps here so
// width can be measured using the correct characters.
label = capitalize(label)
const externalHeight = 20
const internalHeight = 19
const horizPadding = 5
const { totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: externalHeight,
horizPadding,
logoWidth,
logoPadding,
})
const hasMessage = message.length
let { labelWidth, messageWidth } = computeWidths({ label, message })
labelWidth += 10 + totalLogoWidth
messageWidth += 10
messageWidth -= 4
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
const labelTextLength = (labelWidth - (10 + totalLogoWidth)) * 10
const escapedLabel = escapeXml(label)
let [leftLink, rightLink] = links
leftLink = escapeXml(leftLink)
rightLink = escapeXml(rightLink)
const hasLeftLink = leftLink && leftLink.length
const hasRightLink = rightLink && rightLink.length
function renderMessageBubble() {
const messageBubbleMainX = labelWidth + 6.5
const messageBubbleNotchX = labelWidth + 6
return `
<rect x="${messageBubbleMainX}" y="0.5" width="${messageWidth}" height="${internalHeight}" rx="2" fill="#fafafa"/>
<rect x="${messageBubbleNotchX}" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
<path d="M${messageBubbleMainX} 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
`
}
function renderMessageText() {
const messageTextX = (labelWidth + messageWidth / 2 + 6) * 10
const messageTextLength = (messageWidth - 8) * 10
const escapedMessage = escapeXml(message)
const shadow = `<text x="${messageTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
const text = `<text id="rlink" x="${messageTextX}" y="140" transform="scale(.1)" textLength="${messageTextLength}">${escapedMessage}</text>`
if (hasRightLink) {
return `
${shadow}
<a target="_blank" xlink:href="${rightLink}">${text}</a>
`
}
return `
${shadow}
${text}
`
}
function renderLeftLink() {
const rect = `<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x=".5" y=".5" width="${labelWidth}" height="${internalHeight}" rx="2" />`
if (hasLeftLink) {
return `<a target="_blank" xlink:href="${leftLink}">${rect}</a>`
}
return rect
}
const badge = renderBadge(
{
links: [],
leftWidth: labelWidth + 1,
rightWidth: hasMessage ? messageWidth + 6 : 0,
height: externalHeight,
},
`
<style>a #llink:hover{fill:url(#b);stroke:#ccc}a #rlink:hover{fill:#4183c4}</style>
<linearGradient id="a" x2="0" y2="100%">
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#ccc" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<g stroke="#d5d5d5">
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="${labelWidth}" height="${internalHeight}" rx="2"/>
${hasMessage ? renderMessageBubble() : ''}
</g>
${renderedLogo}
<g fill="#333" text-anchor="middle" ${socialFontFamily} font-weight="700" font-size="110px" line-height="14px">
<text x="${labelTextX}" y="150" fill="#fff" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
<text x="${labelTextX}" y="140" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
${hasMessage ? renderMessageText() : ''}
</g>
${renderLeftLink()}
`
)
if (minify) {
return stripXmlWhitespace(badge)
}
return badge
}
function forTheBadge({
label,
message,
links,
logo,
logoWidth,
logoPadding,
color = '#4c1',
labelColor,
minify,
}) {
// For the Badge is styled in all caps. Convert to caps here so widths can
// be measured using the correct characters.
label = label.toUpperCase()
message = message.toUpperCase()
let { labelWidth, messageWidth } = computeWidths({ label, message })
const height = 28
const hasLabel = label.length || labelColor
if (labelColor == null) {
labelColor = '#555'
}
const horizPadding = 9
const { hasLogo, totalLogoWidth, renderedLogo } = renderLogo({
logo,
badgeHeight: height,
horizPadding,
logoWidth,
logoPadding,
})
labelWidth += 10 + totalLogoWidth
if (label.length) {
labelWidth += 10 + label.length * 1.5
} else if (hasLogo) {
if (hasLabel) {
labelWidth += 7
} else {
labelWidth -= 7
}
} else {
labelWidth -= 11
}
messageWidth += 10
messageWidth += 10 + message.length * 2
const leftWidth = hasLogo && !hasLabel ? 0 : labelWidth
const rightWidth =
hasLogo && !hasLabel ? messageWidth + labelWidth : messageWidth
labelColor = hasLabel || hasLogo ? labelColor : color
color = escapeXml(color)
labelColor = escapeXml(labelColor)
function renderLabelText() {
const labelTextX = ((labelWidth + totalLogoWidth) / 2) * 10
const labelTextLength = (labelWidth - (24 + totalLogoWidth)) * 10
const escapedLabel = escapeXml(label)
return `
<text x="${labelTextX}" y="175" transform="scale(.1)" textLength="${labelTextLength}">${escapedLabel}</text>
`
}
const badge = renderBadge(
{
links,
leftWidth,
rightWidth,
height,
},
`
<g shape-rendering="crispEdges">
<rect width="${leftWidth}" height="${height}" fill="${labelColor}"/>
<rect x="${leftWidth}" width="${rightWidth}" height="${height}" fill="${color}"/>
</g>
<g fill="#fff" text-anchor="middle" ${fontFamily} font-size="100">
${renderedLogo}
${hasLabel ? renderLabelText() : ''}
<text x="${(labelWidth + messageWidth / 2) *
10}" y="175" font-weight="bold" transform="scale(.1)" textLength="${(messageWidth -
24) *
10}">
${escapeXml(message)}</text>
</g>`
)
if (minify) {
return stripXmlWhitespace(badge)
}
return badge
}
module.exports = { plastic, flat, flatSquare, social, forTheBadge }

View File

@@ -2,7 +2,7 @@
const isCSSColor = require('is-css-color')
// When updating these, be sure also to update the list in `gh-badges/README.md`.
// When updating these, be sure also to update the list in `badge-maker/README.md`.
const namedColors = {
brightgreen: '#4c1',
green: '#97ca00',

87
badge-maker/lib/index.js Normal file
View File

@@ -0,0 +1,87 @@
'use strict'
/**
* @module badge-maker
*/
const _makeBadge = require('./make-badge')
class ValidationError extends Error {}
function _validate(format) {
if (format !== Object(format)) {
throw new ValidationError('makeBadge takes an argument of type object')
}
if (!('message' in format)) {
throw new ValidationError('Field `message` is required')
}
const stringFields = ['labelColor', 'color', 'message', 'label']
stringFields.forEach(function(field) {
if (field in format && typeof format[field] !== 'string') {
throw new ValidationError(`Field \`${field}\` must be of type string`)
}
})
const styleValues = [
'plastic',
'flat',
'flat-square',
'for-the-badge',
'social',
]
if ('style' in format && !styleValues.includes(format.style)) {
throw new ValidationError(
`Field \`style\` must be one of (${styleValues.toString()})`
)
}
}
function _clean(format) {
const expectedKeys = ['label', 'message', 'labelColor', 'color', 'style']
const cleaned = {}
Object.keys(format).forEach(key => {
if (format[key] != null && expectedKeys.includes(key)) {
cleaned[key] = format[key]
} else {
throw new ValidationError(
`Unexpected field '${key}'. Allowed values are (${expectedKeys.toString()})`
)
}
})
// convert "public" format to "internal" format
cleaned.text = [cleaned.label || '', cleaned.message]
delete cleaned.label
delete cleaned.message
if ('style' in cleaned) {
cleaned.template = cleaned.style
delete cleaned.style
}
return cleaned
}
/**
* Create a badge
*
* @param {object} format Object specifying badge data
* @param {string} format.label (Optional) Badge label (e.g: 'build')
* @param {string} format.message (Required) Badge message (e.g: 'passing')
* @param {string} format.labelColor (Optional) Label color
* @param {string} format.color (Optional) Message color
* @param {string} format.style (Optional) Visual style e.g: 'flat'
* @returns {string} Badge in SVG or JSON format
* @see https://github.com/badges/shields/tree/master/badge-maker/README.md
*/
function makeBadge(format) {
_validate(format)
const cleanedFormat = _clean(format)
return _makeBadge(cleanedFormat)
}
module.exports = {
makeBadge,
ValidationError,
}

View File

@@ -0,0 +1,75 @@
'use strict'
const { expect } = require('chai')
const isSvg = require('is-svg')
const { makeBadge, ValidationError } = require('.')
describe('makeBadge function', function() {
it('should produce badge with valid input', function() {
expect(
makeBadge({
label: 'build',
message: 'passed',
})
).to.satisfy(isSvg)
expect(
makeBadge({
message: 'passed',
})
).to.satisfy(isSvg)
expect(
makeBadge({
label: 'build',
message: 'passed',
color: 'green',
style: 'flat',
})
).to.satisfy(isSvg)
})
it('should throw a ValidationError with invalid inputs', function() {
;[null, undefined, 7, 'foo', 4.25].forEach(x => {
console.log(x)
expect(() => makeBadge(x)).to.throw(
ValidationError,
'makeBadge takes an argument of type object'
)
})
expect(() => makeBadge({})).to.throw(
ValidationError,
'Field `message` is required'
)
expect(() => makeBadge({ label: 'build' })).to.throw(
ValidationError,
'Field `message` is required'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', labelColor: 7 })
).to.throw(ValidationError, 'Field `labelColor` must be of type string')
expect(() =>
makeBadge({ label: 'build', message: 'passed', format: 'png' })
).to.throw(ValidationError, "Unexpected field 'format'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', template: 'flat' })
).to.throw(ValidationError, "Unexpected field 'template'")
expect(() =>
makeBadge({ label: 'build', message: 'passed', foo: 'bar' })
).to.throw(ValidationError, "Unexpected field 'foo'")
expect(() =>
makeBadge({
label: 'build',
message: 'passed',
style: 'something else',
})
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
expect(() =>
makeBadge({ label: 'build', message: 'passed', style: 'popout' })
).to.throw(
ValidationError,
'Field `style` must be one of (plastic,flat,flat-square,for-the-badge,social)'
)
})
})

View File

@@ -0,0 +1,64 @@
'use strict'
const camelcase = require('camelcase')
const { normalizeColor, toSvgColor } = require('./color')
const badgeRenderers = require('./badge-renderers')
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
it is likely this will impact on the package's public interface in index.js
*/
module.exports = function makeBadge({
format,
template = 'flat',
text,
color,
labelColor,
logo,
logoPosition,
logoWidth,
links = ['', ''],
}) {
// String coercion and whitespace removal.
text = text.map(value => `${value}`.trim())
const [label, message] = text
color = normalizeColor(color)
labelColor = normalizeColor(labelColor)
// This ought to be the responsibility of the server, not `makeBadge`.
if (format === 'json') {
return JSON.stringify({
label,
message,
logoWidth,
color,
labelColor,
link: links,
name: label,
value: message,
})
}
const methodName = camelcase(template)
if (!(methodName in badgeRenderers)) {
throw new Error(`Unknown template: '${template}'`)
}
const render = badgeRenderers[methodName]
logoWidth = +logoWidth || (logo ? 14 : 0)
return render({
label,
message,
links,
logo,
logoPosition,
logoWidth,
logoPadding: logo && label.length ? 3 : 0,
color: toSvgColor(color),
labelColor: toSvgColor(labelColor),
minify: true,
})
}

View File

@@ -0,0 +1,571 @@
'use strict'
const { test, given, forCases } = require('sazerac')
const { expect } = require('chai')
const snapshot = require('snap-shot-it')
const isSvg = require('is-svg')
const makeBadge = require('./make-badge')
function testColor(color = '', colorAttr = 'color') {
return JSON.parse(
makeBadge({
text: ['name', 'Bob'],
[colorAttr]: color,
format: 'json',
})
).color
}
describe('The badge generator', function() {
describe('color test', function() {
test(testColor, () => {
// valid hex
forCases([
given('#4c1'),
given('#4C1'),
given('4C1'),
given('4c1'),
]).expect('#4c1')
forCases([
given('#abc123'),
given('#ABC123'),
given('abc123'),
given('ABC123'),
]).expect('#abc123')
// valid rgb(a)
given('rgb(0,128,255)').expect('rgb(0,128,255)')
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
// valid hsl(a)
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
// CSS named color.
given('papayawhip').expect('papayawhip')
// Shields named color.
given('red').expect('red')
given('green').expect('green')
given('blue').expect('blue')
given('yellow').expect('yellow')
// Semantic color alias
given('success').expect('brightgreen')
given('informational').expect('blue')
forCases(
// invalid hex
given('#123red'), // contains letter above F
given('#red'), // contains letter above F
// invalid rgb(a)
given('rgb(220,128,255,0.5)'), // has alpha
given('rgba(0,0,255)'), // no alpha
// invalid hsl(a)
given('hsl(360,50%,50%,0.5)'), // has alpha
given('hsla(0,50%,101%)'), // no alpha
// neither a css named color nor colorscheme
given('notacolor'),
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus')
).expect(undefined)
})
})
describe('color aliases', function() {
test(testColor, () => {
forCases([given('#4c1', 'color')]).expect('#4c1')
})
})
describe('SVG', function() {
it('should produce SVG', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
expect(svg)
.to.satisfy(isSvg)
.and.to.include('cactus')
.and.to.include('grown')
})
it('should match snapshot', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
snapshot(svg)
})
})
describe('JSON', function() {
it('should produce the expected JSON', function() {
const json = makeBadge({
text: ['cactus', 'grown'],
format: 'json',
links: ['https://example.com/', 'https://other.example.com/'],
})
expect(JSON.parse(json)).to.deep.equal({
name: 'cactus',
label: 'cactus',
value: 'grown',
message: 'grown',
link: ['https://example.com/', 'https://other.example.com/'],
})
})
it('should replace undefined svg template with "flat"', function() {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
it('should fail with unknown svg template', function() {
expect(() =>
makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'unknown_style',
})
).to.throw(Error, "Unknown template: 'unknown_style'")
})
})
describe('"flat" template badge generation', function() {
it('should match snapshots: message/label, no logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"flat-square" template badge generation', function() {
it('should match snapshots: message/label, no logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'flat-square',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"plastic" template badge generation', function() {
it('should match snapshots: message/label, no logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'plastic',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"for-the-badge" template badge generation', function() {
// https://github.com/badges/shields/issues/1280
it('numbers should produce a string', function() {
const svg = makeBadge({
text: [1998, 1999],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('1998')
.and.to.include('1999')
})
it('lowercase/mixedcase string should produce uppercase string', function() {
const svg = makeBadge({
text: ['Label', '1 string'],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('LABEL')
.and.to.include('1 STRING')
})
it('should match snapshots: message/label, no logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'for-the-badge',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('"social" template badge generation', function() {
it('should produce capitalized string for badge key', function() {
const svg = makeBadge({
text: ['some-key', 'some-value'],
format: 'svg',
template: 'social',
})
expect(svg)
.to.include('Some-key')
.and.to.include('some-value')
})
// https://github.com/badges/shields/issues/1606
it('should handle empty strings used as badge keys', function() {
const svg = makeBadge({
text: ['', 'some-value'],
format: 'json',
template: 'social',
})
expect(svg)
.to.include('""')
.and.to.include('some-value')
})
it('should match snapshots: message/label, no logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
})
)
})
it('should match snapshots: message/label, with logo', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, no logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
})
)
})
it('should match snapshots: message only, with logo', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message only, with logo and labelColor', function() {
snapshot(
makeBadge({
text: ['', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
)
})
it('should match snapshots: message/label, with links', function() {
snapshot(
makeBadge({
text: ['cactus', 'grown'],
format: 'svg',
template: 'social',
color: '#b3e',
labelColor: '#0f0',
links: ['https://shields.io/', 'https://www.google.co.uk/'],
})
)
})
})
describe('badges with logos should always produce the same badge', function() {
it('badge with logo', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'data:image/svg+xml;base64,PHN2ZyB4bWxu',
})
snapshot(svg)
})
})
})

View File

@@ -1,6 +1,6 @@
{
"name": "gh-badges",
"version": "2.2.1",
"name": "badge-maker",
"version": "3.0.0-rc1",
"description": "Shields.io badge library",
"keywords": [
"GitHub",
@@ -13,7 +13,7 @@
"repository": {
"type": "git",
"url": "git+https://github.com/badges/shields.git",
"directory": "gh-badges"
"directory": "badge-maker"
},
"author": "Thaddée Tyl <thaddee.tyl@gmail.com>",
"license": "CC0-1.0",
@@ -35,10 +35,7 @@
},
"dependencies": {
"anafanafo": "^1.0.0",
"dot": "^1.1.2",
"gm": "^1.23.0",
"is-css-color": "^1.0.0",
"svgo": "^1.1.1"
"is-css-color": "^1.0.0"
},
"scripts": {
"test": "echo 'Run tests from parent dir'; false"

View File

@@ -38,6 +38,7 @@ export function staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
style,
namedLogo,
@@ -46,6 +47,7 @@ export function staticBadgeUrl({
baseUrl?: string
label: string
message: string
labelColor?: string
color?: string
style?: string
namedLogo?: string

View File

@@ -59,6 +59,7 @@ function staticBadgeUrl({
baseUrl = '',
label,
message,
labelColor,
color = 'lightgray',
style,
namedLogo,
@@ -66,6 +67,7 @@ function staticBadgeUrl({
}) {
const path = [label, message, color].map(encodeField).join('-')
const outQueryString = queryString.stringify({
labelColor,
style,
logo: namedLogo,
})

View File

@@ -1,6 +1,6 @@
'use strict'
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseService = require('./base')
const { MetricHelper } = require('./metric-helper')
const { setCacheHeaders } = require('./cache-headers')

View File

@@ -1,6 +1,6 @@
'use strict'
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseService = require('./base')
const {
serverHasBeenUpSinceResourceCached,

View File

@@ -3,7 +3,7 @@
const { expect } = require('chai')
const sinon = require('sinon')
const Joi = require('@hapi/joi')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const BaseSvgScrapingService = require('./base-svg-scraping')
function makeExampleSvg({ label, message }) {

View File

@@ -392,7 +392,7 @@ describe('BaseService', function() {
expect(mockSendBadge).to.have.been.calledWith(expectedFormat, {
text: ['cat', 'Hello namedParamA: bar with queryParamA: ?'],
color: 'lightgrey',
template: undefined,
template: 'flat',
namedLogo: undefined,
logo: undefined,
logoWidth: undefined,

View File

@@ -104,7 +104,23 @@ module.exports = function coalesceBadge(
labelColor: defaultLabelColor,
} = defaultBadgeData
const style = coalesce(overrideStyle, serviceStyle)
let style = coalesce(overrideStyle, serviceStyle)
if (typeof style !== 'string') {
style = 'flat'
}
if (style.startsWith('popout')) {
style = style.replace('popout', 'flat')
}
const styleValues = [
'plastic',
'flat',
'flat-square',
'for-the-badge',
'social',
]
if (!styleValues.includes(style)) {
style = 'flat'
}
let namedLogo, namedLogoColor, logoWidth, logoPosition, logoSvgBase64
if (overrideLogo) {

View File

@@ -278,8 +278,21 @@ describe('coalesceBadge', function() {
})
describe('Style', function() {
it('overrides the template', function() {
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('pill')
it('falls back to flat with invalid style', function() {
expect(coalesceBadge({ style: 'pill' }, {}, {}).template).to.equal('flat')
expect(coalesceBadge({ style: 7 }, {}, {}).template).to.equal('flat')
expect(coalesceBadge({ style: undefined }, {}, {}).template).to.equal(
'flat'
)
})
it('replaces legacy popout styles', function() {
expect(coalesceBadge({ style: 'popout' }, {}, {}).template).to.equal(
'flat'
)
expect(
coalesceBadge({ style: 'popout-square' }, {}, {}).template
).to.equal('flat-square')
})
})

View File

@@ -2,7 +2,7 @@
const request = require('request')
const queryString = require('query-string')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const { setCacheHeaders } = require('./cache-headers')
const {
Inaccessible,

View File

@@ -9,7 +9,7 @@ const { URL } = url
const bytes = require('bytes')
const Camp = require('camp')
const originalJoi = require('@hapi/joi')
const makeBadge = require('../../gh-badges/lib/make-badge')
const makeBadge = require('../../badge-maker/lib/make-badge')
const GithubConstellation = require('../../services/github/github-constellation')
const suggest = require('../../services/suggest')
const { loadServiceClasses } = require('../base-service/loader')

View File

@@ -7,7 +7,7 @@ The Shields codebase is divided into several parts:
1. The frontend (about 7% of the code)
1. [`frontend`][frontend]
2. The badge renderer (which is available as an npm package)
1. [`gh-badges`][gh-badges]
1. [`badge-maker`][badge-maker]
3. The base service classes (about 8% of the code, and probably the most important
code in the codebase)
1. [`core/base-service`][base-service]
@@ -24,7 +24,7 @@ The Shields codebase is divided into several parts:
1. [`lib/suggest.js`][suggest]
[frontend]: https://github.com/badges/shields/tree/master/frontend
[gh-badges]: https://github.com/badges/shields/tree/master/gh-badges
[badge-maker]: https://github.com/badges/shields/tree/master/badge-maker
[base-service]: https://github.com/badges/shields/tree/master/core/base-service
[server]: https://github.com/badges/shields/tree/master/core/server
[token-pooling]: https://github.com/badges/shields/tree/master/core/token-pooling
@@ -36,7 +36,7 @@ The tests are also divided into several parts:
1. Unit and functional tests of the frontend
1. `frontend/**/*.spec.js`
2. Unit and functional tests of the badge renderer
1. `gh-badges/**/*.spec.js`
1. `badge-maker/**/*.spec.js`
3. Unit and functional tests of the core code
1. `core/**/*.spec.js`
4. Unit and functional tests of the service helper functions

View File

@@ -78,7 +78,7 @@ $ docker run --rm -p 8080:80 --name shields shields
# or if you have shields.env file, run the following instead
$ docker run --rm -p 8080:80 --env-file shields.env --name shields shields
> gh-badges@1.1.2 start /usr/src/app
> badge-maker@3.0.0 start /usr/src/app
> node server.js
http://[::1]/

View File

@@ -0,0 +1,154 @@
import React, { Fragment } from 'react'
import styled from 'styled-components'
// @ts-ingnore
import { staticBadgeUrl } from '../../../core/badge-urls/make-badge-url'
import { baseUrl } from '../../constants'
import Meta from '../meta'
// @ts-ignore
import Header from '../header'
import { H3, Badge } from '../common'
const StyledTable = styled.table`
border: 1px solid #ccc;
border-collapse: collapse;
td {
border: 1px solid #ccc;
padding: 3px;
text-align: left;
}
`
interface BadgeData {
label: string
message: string
labelColor?: string
color: string
namedLogo?: string
}
function Badges({
baseUrl,
style,
badges,
}: {
baseUrl: string
style: string
badges: BadgeData[]
}): JSX.Element {
return (
<>
{badges.map(({ label, message, labelColor, color, namedLogo }) => (
<Fragment key={`${label}-${message}-${color}-${namedLogo}`}>
<Badge
alt="build"
src={staticBadgeUrl({
baseUrl,
label,
message,
labelColor,
color,
namedLogo,
style,
})}
/>
<br />
</Fragment>
))}
</>
)
}
interface StyleExamples {
title: string
badges: BadgeData[]
}
const examples = [
{
title: 'Basic examples',
badges: [
{ label: 'build', message: 'passing', color: 'brightgreen' },
{ label: 'tests', message: '5 passing, 1 failed', color: 'red' },
{ label: 'python', message: '3.5 | 3.6 | 3.7', color: 'blue' },
],
},
{
title: 'Logo',
badges: [
{
label: 'build',
message: 'passing',
color: 'brightgreen',
namedLogo: 'appveyor',
},
],
},
{
title: 'No left text',
badges: [
{ label: '', message: 'blueviolet', color: 'blueviolet' },
{
label: '',
message: 'passing',
color: 'brightgreen',
namedLogo: 'appveyor',
},
{
label: '',
message: 'passing',
color: 'brightgreen',
labelColor: 'grey',
namedLogo: 'appveyor',
},
],
},
]
function StyleTable({ style }: { style: string }): JSX.Element {
return (
<StyledTable>
<thead>
<tr>
<td>Description</td>
<td>Badges (new)</td>
<td>Badges (old)</td>
</tr>
</thead>
<tbody>
{examples.map(({ title, badges }) => (
<tr key={title}>
<td>{title}</td>
<td>
<Badges badges={badges} baseUrl={baseUrl} style={style} />
</td>
<td>
<Badges
badges={badges}
baseUrl="http://img.shields.io"
style={style}
/>
</td>
</tr>
))}
</tbody>
</StyledTable>
)
}
const styles = ['flat', 'flat-square', 'for-the-badge', 'social', 'plastic']
export default function StylePage(): JSX.Element {
return (
<div>
<Meta />
<Header />
{styles.map(style => (
<Fragment key={style}>
<H3>{style}</H3>
<StyleTable style={style} />
</Fragment>
))}
</div>
)
}

View File

@@ -21,6 +21,12 @@ const { categories } = yaml.safeLoad(
// https://www.gatsbyjs.org/docs/using-gatsby-without-graphql/#the-approach-fetch-data-and-use-gatsbys-createpages-api
async function createPages({ actions: { createPage } }) {
if (includeDevPages) {
createPage({
path: '/dev/styles',
component: require.resolve(
'./frontend/components/development/style-page.tsx'
),
})
createPage({
path: '/dev/logos',
component: require.resolve(

View File

@@ -1,43 +0,0 @@
'use strict'
/**
* @module gh-badges
*/
const makeBadge = require('./make-badge')
/**
* BadgeFactory
*/
class BadgeFactory {
constructor(options) {
if (options !== undefined) {
console.error(
'BadgeFactory: Constructor options are deprecated and will be ignored'
)
}
}
/**
* Create a badge
*
* @param {object} format Object specifying badge data
* @param {string[]} format.text Badge text in an array e.g: ['build', 'passing']
* @param {string} format.labelColor (Optional) Label color
* @param {string} format.color (Optional) Message color
* @param {string} format.colorA (Deprecated, Optional) alias for `labelColor`
* @param {string} format.colorscheme (Deprecated, Optional) alias for `color`
* @param {string} format.colorB (Deprecated, Optional) alias for `color`
* @param {string} format.format (Optional) Output format: 'svg' or 'json'
* @param {string} format.template (Optional) Visual template e.g: 'flat'
* see {@link https://github.com/badges/shields/tree/master/gh-badges/templates}
* @returns {string} Badge in SVG or JSON format
* @see https://github.com/badges/shields/tree/master/gh-badges/README.md
*/
create(format) {
return makeBadge(format)
}
}
module.exports = {
BadgeFactory,
}

View File

@@ -1,20 +0,0 @@
'use strict'
const { expect } = require('chai')
const isSvg = require('is-svg')
const { BadgeFactory } = require('.')
const bf = new BadgeFactory()
describe('BadgeFactory class', function() {
it('should produce badge with valid input', function() {
expect(
bf.create({
text: ['build', 'passed'],
format: 'svg',
colorscheme: 'green',
template: 'flat',
})
).to.satisfy(isSvg)
})
})

View File

@@ -1,185 +0,0 @@
'use strict'
const fs = require('fs')
const path = require('path')
const SVGO = require('svgo')
const dot = require('dot')
const anafanafo = require('anafanafo')
const { normalizeColor, toSvgColor } = require('./color')
// cache templates.
const templates = {}
const templateFiles = fs.readdirSync(path.join(__dirname, '..', 'templates'))
dot.templateSettings.strip = false // Do not strip whitespace.
templateFiles.forEach(async filename => {
if (filename[0] === '.') {
return
}
const templateData = fs
.readFileSync(path.join(__dirname, '..', 'templates', filename))
.toString()
const extension = path.extname(filename).slice(1)
const style = filename.slice(0, -`-template.${extension}`.length)
// Compile the template. Necessary to always have a working template.
templates[style] = dot.template(templateData)
// Substitute dot code.
const mapping = new Map()
let mappingIndex = 1
const untemplatedSvg = templateData.replace(/{{.*?}}/g, match => {
// Weird substitution that currently works for all templates.
const mapKey = `99999990${mappingIndex}.1`
mappingIndex++
mapping.set(mapKey, match)
return mapKey
})
const svgo = new SVGO()
const { data, error } = await svgo.optimize(untemplatedSvg)
if (error !== undefined) {
console.error(
`Template ${filename}: ${error}\n` +
' Generated untemplated SVG:\n' +
`---\n${untemplatedSvg}---\n`
)
return
}
// Substitute dot code back.
let svg = data
const unmappedKeys = []
mapping.forEach((value, key) => {
let keySubstituted = false
svg = svg.replace(RegExp(key, 'g'), () => {
keySubstituted = true
return value
})
if (!keySubstituted) {
unmappedKeys.push(key)
}
})
if (unmappedKeys.length > 0) {
console.error(
`Template ${filename} has unmapped keys ` +
`${unmappedKeys.join(', ')}.\n` +
' Generated untemplated SVG:\n' +
`---\n${untemplatedSvg}\n---\n` +
' Generated template:\n' +
`---\n${svg}\n---\n`
)
return
}
templates[style] = dot.template(svg)
})
function escapeXml(s) {
if (s === undefined || typeof s !== 'string') {
return undefined
} else {
return s
.replace(/&/g, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&apos;')
}
}
function capitalize(s) {
return s.charAt(0).toUpperCase() + s.slice(1)
}
/*
note: makeBadge() is fairly thinly wrapped so if we are making changes here
it is likely this will impact on the package's public interface in index.js
*/
module.exports = function makeBadge({
format,
template,
text,
colorscheme,
color,
colorA,
colorB,
labelColor,
logo,
logoPosition,
logoWidth,
links = ['', ''],
}) {
// String coercion and whitespace removal.
text = text.map(value => `${value}`.trim())
let [left, right] = text
color = normalizeColor(color || colorB || colorscheme)
labelColor = normalizeColor(labelColor || colorA)
// This ought to be the responsibility of the server, not `makeBadge`.
if (format === 'json') {
return JSON.stringify({
label: left,
message: right,
logoWidth,
color,
labelColor,
link: links,
name: left,
value: right,
})
}
if (!(template in templates)) {
if (template === 'popout-square') {
template = 'flat-square'
} else {
template = 'flat'
}
}
if (template === 'social') {
left = capitalize(left)
} else if (template === 'for-the-badge') {
left = left.toUpperCase()
right = right.toUpperCase()
}
let leftWidth = (anafanafo(left) / 10) | 0
// Increase chances of pixel grid alignment.
if (leftWidth % 2 === 0) {
leftWidth++
}
let rightWidth = (anafanafo(right) / 10) | 0
// Increase chances of pixel grid alignment.
if (rightWidth % 2 === 0) {
rightWidth++
}
logoWidth = +logoWidth || (logo ? 14 : 0)
let logoPadding
if (left.length === 0) {
logoPadding = 0
} else {
logoPadding = logo ? 3 : 0
}
const context = {
text: [left, right],
escapedText: [left, right].map(escapeXml),
widths: [leftWidth + 10 + logoWidth + logoPadding, rightWidth + 10],
links: links.map(escapeXml),
logo: escapeXml(logo),
logoPosition,
logoWidth,
logoPadding,
colorA: toSvgColor(labelColor),
colorB: toSvgColor(color),
escapeXml,
}
const templateFn = templates[template]
// The call to template() can raise an exception.
return templateFn(context)
}

View File

@@ -1,231 +0,0 @@
'use strict'
const { test, given, forCases } = require('sazerac')
const { expect } = require('chai')
const snapshot = require('snap-shot-it')
const isSvg = require('is-svg')
const makeBadge = require('./make-badge')
function testColor(color = '', colorAttr = 'colorB') {
return JSON.parse(
makeBadge({
text: ['name', 'Bob'],
[colorAttr]: color,
format: 'json',
})
).color
}
describe('The badge generator', function() {
describe('color test', function() {
test(testColor, () => {
// valid hex
forCases([
given('#4c1'),
given('#4C1'),
given('4C1'),
given('4c1'),
]).expect('#4c1')
forCases([
given('#abc123'),
given('#ABC123'),
given('abc123'),
given('ABC123'),
]).expect('#abc123')
// valid rgb(a)
given('rgb(0,128,255)').expect('rgb(0,128,255)')
given('rgba(0,128,255,0)').expect('rgba(0,128,255,0)')
// valid hsl(a)
given('hsl(100, 56%, 10%)').expect('hsl(100, 56%, 10%)')
given('hsla(25,20%,0%,0.1)').expect('hsla(25,20%,0%,0.1)')
// CSS named color.
given('papayawhip').expect('papayawhip')
// Shields named color.
given('red').expect('red')
given('green').expect('green')
given('blue').expect('blue')
given('yellow').expect('yellow')
forCases(
// invalid hex
given('#123red'), // contains letter above F
given('#red'), // contains letter above F
// invalid rgb(a)
given('rgb(220,128,255,0.5)'), // has alpha
given('rgba(0,0,255)'), // no alpha
// invalid hsl(a)
given('hsl(360,50%,50%,0.5)'), // has alpha
given('hsla(0,50%,101%)'), // no alpha
// neither a css named color nor colorscheme
given('notacolor'),
given('bluish'),
given('almostred'),
given('brightmaroon'),
given('cactus')
).expect(undefined)
})
})
describe('color aliases', function() {
test(testColor, () => {
forCases([
given('#4c1', 'color'),
given('#4c1', 'colorB'),
given('#4c1', 'colorscheme'),
]).expect('#4c1')
})
})
describe('SVG', function() {
it('should produce SVG', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
expect(svg)
.to.satisfy(isSvg)
.and.to.include('cactus')
.and.to.include('grown')
})
it('should always produce the same SVG (unless we have changed something!)', function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
snapshot(svg)
})
})
describe('JSON', function() {
it('should produce the expected JSON', function() {
const json = makeBadge({
text: ['cactus', 'grown'],
format: 'json',
links: ['https://example.com/', 'https://other.example.com/'],
})
expect(JSON.parse(json)).to.deep.equal({
name: 'cactus',
label: 'cactus',
value: 'grown',
message: 'grown',
link: ['https://example.com/', 'https://other.example.com/'],
})
})
it('should replace unknown svg template with "flat"', function() {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'unknown_style',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
it('should replace "popout-square" svg template with "flat-square"', function() {
const jsonBadgeWithUnknownStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'popout-square',
})
const jsonBadgeWithDefaultStyle = makeBadge({
text: ['name', 'Bob'],
format: 'svg',
template: 'flat-square',
})
expect(jsonBadgeWithUnknownStyle)
.to.equal(jsonBadgeWithDefaultStyle)
.and.to.satisfy(isSvg)
})
})
describe('"for-the-badge" template badge generation', function() {
// https://github.com/badges/shields/issues/1280
it('numbers should produce a string', function() {
const svg = makeBadge({
text: [1998, 1999],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('1998')
.and.to.include('1999')
})
it('lowercase/mixedcase string should produce uppercase string', function() {
const svg = makeBadge({
text: ['Label', '1 string'],
format: 'svg',
template: 'for-the-badge',
})
expect(svg)
.to.include('LABEL')
.and.to.include('1 STRING')
})
})
describe('"social" template badge generation', function() {
it('should produce capitalized string for badge key', function() {
const svg = makeBadge({
text: ['some-key', 'some-value'],
format: 'svg',
template: 'social',
})
expect(svg)
.to.include('Some-key')
.and.to.include('some-value')
})
// https://github.com/badges/shields/issues/1606
it('should handle empty strings used as badge keys', function() {
const svg = makeBadge({
text: ['', 'some-value'],
format: 'json',
template: 'social',
})
expect(svg)
.to.include('""')
.and.to.include('some-value')
})
})
describe('badges with logos should always produce the same badge', function() {
it('shields GitHub logo default color (#333333)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'github',
})
snapshot(svg)
})
it('shields GitHub logo custom color (whitesmoke)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'github',
logoColor: 'whitesmoke',
})
snapshot(svg)
})
it('simple-icons javascript logo default color (#F7DF1E)', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'javascript',
})
snapshot(svg)
})
it('simple-icons javascript logo custom color (rgba(46,204,113,0.8))', function() {
const svg = makeBadge({
text: ['label', 'message'],
format: 'svg',
logo: 'javascript',
logoColor: 'rgba(46,204,113,0.8)',
})
snapshot(svg)
})
})
})

View File

@@ -1,25 +0,0 @@
'use strict'
const { promisify } = require('util')
const gm = require('gm')
const imageMagick = gm.subClass({ imageMagick: true })
async function svgToImg(svg, format) {
const svgBuffer = Buffer.from(
`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>${svg}`
)
const chain = imageMagick(svgBuffer, `image.${format}`)
.density(90)
.background(format === 'jpg' ? '#FFFFFF' : 'none')
.flatten()
const toBuffer = chain.toBuffer.bind(chain)
return promisify(toBuffer)(format)
}
module.exports = svgToImg
// To simplify testing.
module.exports._imageMagick = imageMagick

View File

@@ -1,14 +0,0 @@
'use strict'
const { expect } = require('chai')
const isPng = require('is-png')
const svg2img = require('./svg-to-img')
const makeBadge = require('./make-badge')
describe('The rasterizer', function() {
it('should produce PNG', async function() {
const svg = makeBadge({ text: ['cactus', 'grown'], format: 'svg' })
const data = await svg2img(svg, 'png')
expect(data).to.satisfy(isPng)
})
})

View File

@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{=(it.widths[0] -= it.text[0].length ? 0 : (it.logo ? (it.colorA ? 0 : 7) : 11))+it.widths[1]}}" height="20">
<g shape-rendering="crispEdges">
<rect width="{{=it.widths[0]}}" height="20" fill="{{=it.escapeXml(it.text[0].length || it.logo && it.colorA ? (it.colorA||"#555") : (it.colorB||"#4c1"))}}"/>
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="20" fill="{{=it.escapeXml(it.colorB||"#4c1")}}"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
{{?it.logo}}
<image x="5" y="3" width="{{=it.logoWidth}}" height="14" xlink:href="{{=it.logo}}"/>
{{?}}
{{?it.text[0].length}}
<text x="{{=(((it.widths[0]+it.logoWidth+it.logoPadding)/2)+1)*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
{{?}}
<text x="{{=(it.widths[0]+it.widths[1]/2-(it.text[0].length ? 1 : 0 ))*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[1]-10)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
</g>
{{?(it.links[0] && it.links[0].length)}}
<a target="_blank" xlink:href="{{=it.links[0]}}">
<rect width="{{=it.widths[0]}}" height="20" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}
<a target="_blank" xlink:href="{{=it.links[1] || it.links[0]}}">
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="20" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -1,39 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{=(it.widths[0] -= it.text[0].length ? 0 : (it.logo ? (it.colorA ? 0 : 7) : 11))+it.widths[1]}}" height="20">
<linearGradient id="smooth" x2="0" y2="100%">
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<clipPath id="round">
<rect width="{{=it.widths[0]+it.widths[1]}}" height="20" rx="3" fill="#fff"/>
</clipPath>
<g clip-path="url(#round)">
<rect width="{{=it.widths[0]}}" height="20" fill="{{=it.escapeXml(it.text[0].length || it.logo && it.colorA ? (it.colorA||"#555") : (it.colorB||"#4c1"))}}"/>
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="20" fill="{{=it.escapeXml(it.colorB||"#4c1")}}"/>
<rect width="{{=it.widths[0]+it.widths[1]}}" height="20" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
{{?it.logo}}
<image x="5" y="3" width="{{=it.logoWidth}}" height="14" xlink:href="{{=it.logo}}"/>
{{?}}
{{?it.text[0].length}}
<text x="{{=(((it.widths[0]+it.logoWidth+it.logoPadding)/2)+1)*10}}" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
<text x="{{=(((it.widths[0]+it.logoWidth+it.logoPadding)/2)+1)*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
{{?}}
<text x="{{=(it.widths[0]+it.widths[1]/2-(it.text[0].length ? 1 : 0 ))*10}}" y="150" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{=(it.widths[1]-10)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
<text x="{{=(it.widths[0]+it.widths[1]/2-(it.text[0].length ? 1 : 0 ))*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[1]-10)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
</g>
{{?(it.links[0] && it.links[0].length)}}
<a target="_blank" xlink:href="{{=it.links[0]}}">
<rect width="{{=it.widths[0]}}" height="20" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}
<a target="_blank" xlink:href="{{=it.links[1] || it.links[0]}}">
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="20" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
</svg>

Before

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,25 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{=(it.widths[0] -= it.text[0].length ? -(10+(it.text[0].length*1.5)) : (it.logo ? (it.colorA ? -7 : 7) : 11))+(it.widths[1]+=(10+(it.text[1].length*2)))}}" height="28">
<g shape-rendering="crispEdges">
<rect width="{{=it.widths[0]}}" height="28" fill="{{=it.escapeXml(it.text[0].length || it.logo && it.colorA ? (it.colorA||"#555") : (it.colorB||"#4c1"))}}"/>
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="28" fill="{{=it.escapeXml(it.colorB||"#4c1")}}"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="100">
{{?it.logo}}
<image x="9" y="7" width="{{=it.logoWidth}}" height="14" xlink:href="{{=it.logo}}"/>
{{?}}
{{?it.text[0].length}}
<text x="{{=((it.widths[0]+it.logoWidth+it.logoPadding)/2)*10}}" y="175" transform="scale(0.1)" textLength="{{=(it.widths[0]-(24+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
{{?}}
<text x="{{=(it.widths[0]+it.widths[1]/2)*10}}" y="175" font-weight="bold" transform="scale(0.1)" textLength="{{=(it.widths[1]-24)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
</g>
{{?(it.text[0].length && it.links[0] && it.links[0].length)}}
<a target="_blank" xlink:href="{{=it.links[0]}}">
<rect width="{{=it.widths[0]}}" height="28" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}
<a target="_blank" xlink:href="{{=it.links[1] || it.links[0]}}">
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="28" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
</svg>

Before

Width:  |  Height:  |  Size: 1.7 KiB

View File

@@ -1,41 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{=(it.widths[0] -= it.text[0].length ? 0 : (it.logo ? (it.colorA ? 0 : 7) : 11))+it.widths[1]}}" height="18">
<linearGradient id="smooth" x2="0" y2="100%">
<stop offset="0" stop-color="#fff" stop-opacity=".7"/>
<stop offset=".1" stop-color="#aaa" stop-opacity=".1"/>
<stop offset=".9" stop-color="#000" stop-opacity=".3"/>
<stop offset="1" stop-color="#000" stop-opacity=".5"/>
</linearGradient>
<clipPath id="round">
<rect width="{{=it.widths[0]+it.widths[1]}}" height="18" rx="4" fill="#fff"/>
</clipPath>
<g clip-path="url(#round)">
<rect width="{{=it.widths[0]}}" height="18" fill="{{=it.escapeXml(it.text[0].length || it.logo && it.colorA ? (it.colorA||"#555") : (it.colorB||"#4c1"))}}"/>
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="18" fill="{{=it.escapeXml(it.colorB||"#4c1")}}"/>
<rect width="{{=it.widths[0]+it.widths[1]}}" height="18" fill="url(#smooth)"/>
</g>
<g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="110">
{{?it.logo}}
<image x="5" y="3" width="{{=it.logoWidth}}" height="14" xlink:href="{{=it.logo}}"/>
{{?}}
{{?it.text[0].length}}
<text x="{{=(((it.widths[0]+it.logoWidth+it.logoPadding)/2)+1)*10}}" y="140" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
<text x="{{=(((it.widths[0]+it.logoWidth+it.logoPadding)/2)+1)*10}}" y="130" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
{{?}}
<text x="{{=(it.widths[0]+it.widths[1]/2-(it.text[0].length ? 1 : 0))*10}}" y="140" fill="#010101" fill-opacity=".3" transform="scale(0.1)" textLength="{{=(it.widths[1]-10)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
<text x="{{=(it.widths[0]+it.widths[1]/2-(it.text[0].length ? 1 : 0))*10}}" y="130" transform="scale(0.1)" textLength="{{=(it.widths[1]-10)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
</g>
{{?(it.links[0] && it.links[0].length)}}
<a target="_blank" xlink:href="{{=it.links[0]}}">
<rect width="{{=it.widths[0]}}" height="18" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}
<a target="_blank" xlink:href="{{=it.links[1] || it.links[0]}}">
<rect x="{{=it.widths[0]}}" width="{{=it.widths[1]}}" height="18" fill="rgba(0,0,0,0)"/>
</a>
{{?}}
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,39 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{{=it.widths[0]+1 + (it.text[1] && it.text[1].length > 0 ? it.widths[1]+2 : 0)}}" height="20">
{{it.widths[1]-=4;}}
<style type="text/css"><![CDATA[
a #llink:hover { fill:url(#b); stroke:#ccc; }
a #rlink:hover { fill:#4183C4; }
]]></style>
<linearGradient id="a" x2="0" y2="100%">
<stop offset="0" stop-color="#fcfcfc" stop-opacity="0"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<linearGradient id="b" x2="0" y2="100%">
<stop offset="0" stop-color="#ccc" stop-opacity=".1"/>
<stop offset="1" stop-opacity=".1"/>
</linearGradient>
<g stroke="#d5d5d5">
<rect stroke="none" fill="#fcfcfc" x="0.5" y="0.5" width="{{=it.widths[0]}}" height="19" rx="2"/>
{{?(it.text[1] && it.text[1].length)}}
<rect y="0.5" x="{{=it.widths[0]+6.5}}" width="{{=it.widths[1]}}" height="19" rx="2" fill="#fafafa"/>
<rect x="{{=it.widths[0]+6}}" y="7.5" width="0.5" height="5" stroke="#fafafa"/>
<path d="M{{=it.widths[0]+6.5}} 6.5 l-3 3v1 l3 3" stroke="d5d5d5" fill="#fafafa"/>
{{?}}
</g>
{{?it.logo}}
<image x="5" y="3" width="{{=it.logoWidth}}" height="14" xlink:href="{{=it.logo}}"/>
{{?}}
<g fill="#333" text-anchor="middle" font-family="Helvetica Neue,Helvetica,Arial,sans-serif" font-weight="700" font-size="110px" line-height="14px">
<text x="{{=((it.widths[0]+it.logoWidth+it.logoPadding)/2)*10}}" y="150" fill="#fff" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
<text x="{{=((it.widths[0]+it.logoWidth+it.logoPadding)/2)*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[0]-(10+it.logoWidth+it.logoPadding))*10}}" lengthAdjust="spacing">{{=it.escapedText[0]}}</text>
{{?(it.text[1] && it.text[1].length)}}
<text x="{{=(it.widths[0]+it.widths[1]/2+6)*10}}" y="150" fill="#fff" transform="scale(0.1)" textLength="{{=(it.widths[1]-8)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}<a target="_blank" xlink:href="{{=it.links[1] || it.links[0]}}">{{?}}
<text id="rlink" x="{{=(it.widths[0]+it.widths[1]/2+6)*10}}" y="140" transform="scale(0.1)" textLength="{{=(it.widths[1]-8)*10}}" lengthAdjust="spacing">{{=it.escapedText[1]}}</text>
{{?(it.links[0] && it.links[0].length || it.links[1] && it.links[1].length)}}</a>{{?}}
{{?}}
</g>
{{?(it.links[0] && it.links[0].length)}}<a target="_blank" xlink:href="{{=it.links[0]}}">{{?}}
<rect id="llink" stroke="#d5d5d5" fill="url(#a)" x="0.5" y="0.5" width="{{=it.widths[0]}}" height="19" rx="2"/>
{{?(it.links[0] && it.links[0].length)}}</a>{{?}}
</svg>

Before

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -1,7 +1,7 @@
'use strict'
const Joi = require('@hapi/joi')
const { toSvgColor } = require('../gh-badges/lib/color')
const { toSvgColor } = require('../badge-maker/lib/color')
const coalesce = require('../core/base-service/coalesce')
const { svg2base64 } = require('./svg-helpers')
const logos = require('./load-logos')()

179
package-lock.json generated
View File

@@ -3259,7 +3259,8 @@
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
"integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ=="
"integrity": "sha512-QD1PhQk+s31P1ixsX0H0Suoupp3VMXzIVMSwobR3F3MSUO2YCV0B7xqLcUw/Bh8yuvd3LhpyqLQWTNcRmp6IdQ==",
"dev": true
},
"@hapi/bourne": {
"version": "1.3.2",
@@ -4920,6 +4921,14 @@
"integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=",
"dev": true
},
"anafanafo": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/anafanafo/-/anafanafo-1.0.0.tgz",
"integrity": "sha512-pDPbI7SFRHu0byMXHAf+v74+LCcHSxnLYkcbfiV91XRlE+NSLpFCpEQdVUy9ZxZw/LuhTrOin4r8wlR3OFrKBA==",
"requires": {
"char-width-table-consumer": "^1.0.0"
}
},
"ansi-align": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz",
@@ -5111,22 +5120,12 @@
"integrity": "sha1-iKK6tz0c97zVwbEYoAP2b2ZfpmI=",
"dev": true
},
"array-parallel": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/array-parallel/-/array-parallel-0.1.3.tgz",
"integrity": "sha1-j3hTCJJu1apHjEfmTRszS2wMlH0="
},
"array-reduce": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz",
"integrity": "sha1-FziZ0//Rx9k4PkR5Ul2+J4yrXys=",
"dev": true
},
"array-series": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/array-series/-/array-series-0.1.5.tgz",
"integrity": "sha1-3103v8XC7wdV4qpPkv6ufUtaly8="
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
@@ -6697,6 +6696,13 @@
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
},
"badge-maker": {
"version": "file:badge-maker",
"requires": {
"anafanafo": "^1.0.0",
"is-css-color": "^1.0.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -6827,6 +6833,11 @@
"integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==",
"dev": true
},
"binary-search": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.6.tgz",
"integrity": "sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA=="
},
"bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
@@ -6910,7 +6921,8 @@
"boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
"dev": true
},
"boxen": {
"version": "4.2.0",
@@ -7851,6 +7863,14 @@
}
}
},
"char-width-table-consumer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/char-width-table-consumer/-/char-width-table-consumer-1.0.0.tgz",
"integrity": "sha512-Fz4UD0LBpxPgL9i29CJ5O4KANwaMnX/OhhbxzvNa332h+9+nRKyeuLw4wA51lt/ex67+/AdsoBQJF3kgX2feYQ==",
"requires": {
"binary-search": "^1.3.5"
}
},
"chardet": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
@@ -8514,6 +8534,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/coa/-/coa-2.0.1.tgz",
"integrity": "sha512-5wfTTO8E2/ja4jFSxePXlG5nRu5bBtL/r1HCIpJW/lzT6yDtKl0u0Z4o/Vpz32IpKmBn7HerheEZQgA9N2DarQ==",
"dev": true,
"requires": {
"q": "^1.1.2"
}
@@ -8583,7 +8604,8 @@
"colors": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz",
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM="
"integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=",
"dev": true
},
"combined-stream": {
"version": "1.0.6",
@@ -9195,6 +9217,7 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
"integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
"dev": true,
"requires": {
"lru-cache": "^4.0.1",
"which": "^1.2.9"
@@ -9339,7 +9362,8 @@
"css-select-base-adapter": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.0.tgz",
"integrity": "sha1-AQKz0UYw34bD65+p9UVicBBs+ZA="
"integrity": "sha1-AQKz0UYw34bD65+p9UVicBBs+ZA=",
"dev": true
},
"css-selector-tokenizer": {
"version": "0.7.2",
@@ -9367,6 +9391,7 @@
"version": "1.0.0-alpha.28",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz",
"integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==",
"dev": true,
"requires": {
"mdn-data": "~1.1.0",
"source-map": "^0.5.3"
@@ -9375,12 +9400,14 @@
"css-url-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz",
"integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w="
"integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=",
"dev": true
},
"css-what": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz",
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0="
"integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=",
"dev": true
},
"cssesc": {
"version": "3.0.0",
@@ -9469,6 +9496,7 @@
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
"integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==",
"dev": true,
"requires": {
"css-tree": "1.0.0-alpha.29"
},
@@ -9477,6 +9505,7 @@
"version": "1.0.0-alpha.29",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz",
"integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==",
"dev": true,
"requires": {
"mdn-data": "~1.1.0",
"source-map": "^0.5.3"
@@ -10325,6 +10354,7 @@
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
"integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=",
"dev": true,
"requires": {
"domelementtype": "~1.1.1",
"entities": "~1.1.1"
@@ -10333,7 +10363,8 @@
"domelementtype": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz",
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs="
"integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=",
"dev": true
}
}
},
@@ -10352,7 +10383,8 @@
"domelementtype": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz",
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI="
"integrity": "sha1-sXrtguirWeUt2cGbF1bg/BhyBMI=",
"dev": true
},
"domhandler": {
"version": "2.3.0",
@@ -10373,11 +10405,6 @@
"domelementtype": "1"
}
},
"dot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/dot/-/dot-1.1.2.tgz",
"integrity": "sha1-xzdwGfxOVQeYkosrmv62ar+h8vk="
},
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
@@ -10662,7 +10689,8 @@
"entities": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
"integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=",
"dev": true
},
"envinfo": {
"version": "7.5.0",
@@ -16118,48 +16146,6 @@
"assert-plus": "^1.0.0"
}
},
"gh-badges": {
"version": "file:gh-badges",
"requires": {
"anafanafo": "^1.0.0",
"dot": "^1.1.2",
"gm": "^1.23.0",
"is-css-color": "^1.0.0",
"svgo": "^1.1.1"
},
"dependencies": {
"@hapi/joi": {
"version": "15.0.3",
"resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-15.0.3.tgz",
"integrity": "sha512-z6CesJ2YBwgVCi+ci8SI8zixoj8bGFn/vZb9MBPbSyoxsS2PnWYjHcyTM17VLK6tx64YVK38SDIh10hJypB+ig==",
"requires": {
"@hapi/address": "2.x.x",
"@hapi/topo": "3.x.x"
}
},
"anafanafo": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/anafanafo/-/anafanafo-1.0.0.tgz",
"integrity": "sha512-pDPbI7SFRHu0byMXHAf+v74+LCcHSxnLYkcbfiV91XRlE+NSLpFCpEQdVUy9ZxZw/LuhTrOin4r8wlR3OFrKBA==",
"requires": {
"char-width-table-consumer": "^1.0.0"
}
},
"binary-search": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/binary-search/-/binary-search-1.3.5.tgz",
"integrity": "sha512-RHFP0AdU6KAB0CCZsRMU2CJTk2EpL8GLURT+4gilpjr1f/7M91FgUMnXuQLmf3OKLet34gjuNFwO7e4agdX5pw=="
},
"char-width-table-consumer": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/char-width-table-consumer/-/char-width-table-consumer-1.0.0.tgz",
"integrity": "sha512-Fz4UD0LBpxPgL9i29CJ5O4KANwaMnX/OhhbxzvNa332h+9+nRKyeuLw4wA51lt/ex67+/AdsoBQJF3kgX2feYQ==",
"requires": {
"binary-search": "^1.3.5"
}
}
}
},
"git-config-path": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/git-config-path/-/git-config-path-1.0.1.tgz",
@@ -16422,32 +16408,6 @@
}
}
},
"gm": {
"version": "1.23.1",
"resolved": "https://registry.npmjs.org/gm/-/gm-1.23.1.tgz",
"integrity": "sha1-Lt7rlYCE0PjqeYjl2ZWxx9/BR3c=",
"requires": {
"array-parallel": "~0.1.3",
"array-series": "~0.1.5",
"cross-spawn": "^4.0.0",
"debug": "^3.1.0"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
}
}
},
"got": {
"version": "9.6.0",
"resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz",
@@ -20436,6 +20396,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
"integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==",
"dev": true,
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
@@ -20605,7 +20566,8 @@
"mdn-data": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz",
"integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA=="
"integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==",
"dev": true
},
"mdurl": {
"version": "1.0.1",
@@ -20621,7 +20583,7 @@
},
"media-typer": {
"version": "0.3.0",
"resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
"integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=",
"dev": true
},
@@ -22289,6 +22251,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz",
"integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=",
"dev": true,
"requires": {
"boolbase": "~1.0.0"
}
@@ -22871,6 +22834,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
"integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.5.1"
@@ -22897,6 +22861,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz",
"integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"es-abstract": "^1.6.1",
@@ -24768,7 +24733,8 @@
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
},
"psl": {
"version": "1.1.29",
@@ -24854,7 +24820,8 @@
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
"integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=",
"dev": true
},
"qs": {
"version": "6.5.2",
@@ -26453,7 +26420,8 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==",
"dev": true
},
"sazerac": {
"version": "1.1.0",
@@ -27711,7 +27679,8 @@
"stable": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
"integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w=="
"integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==",
"dev": true
},
"stack-trace": {
"version": "0.0.10",
@@ -28866,6 +28835,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/svgo/-/svgo-1.1.1.tgz",
"integrity": "sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g==",
"dev": true,
"requires": {
"coa": "~2.0.1",
"colors": "~1.1.2",
@@ -28887,6 +28857,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.0.tgz",
"integrity": "sha512-MGhoq1S9EyPgZIGnts8Yz5WwUOyHmPMdlqeifsYs/xFX7AAm3hY0RJe1dqVlXtYPI66Nsk39R/sa5/ree6L2qg==",
"dev": true,
"requires": {
"boolbase": "^1.0.0",
"css-what": "2.1",
@@ -28898,6 +28869,7 @@
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz",
"integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==",
"dev": true,
"requires": {
"dom-serializer": "0",
"domelementtype": "1"
@@ -28906,12 +28878,14 @@
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"dev": true,
"requires": {
"minimist": "0.0.8"
}
@@ -29691,7 +29665,8 @@
"unquote": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz",
"integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ="
"integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=",
"dev": true
},
"unset-value": {
"version": "1.0.0",
@@ -30009,6 +29984,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
"integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
"dev": true,
"requires": {
"define-properties": "^1.1.2",
"object.getownpropertydescriptors": "^2.0.3"
@@ -30928,7 +30904,8 @@
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
},
"yaml": {
"version": "1.7.2",

View File

@@ -39,7 +39,7 @@
"escape-string-regexp": "^2.0.0",
"fast-xml-parser": "^3.16.0",
"fsos": "^1.1.6",
"gh-badges": "file:gh-badges",
"badge-maker": "file:badge-maker",
"glob": "^7.1.6",
"graphql": "^14.6.0",
"graphql-tag": "^2.10.3",
@@ -84,7 +84,7 @@
"test:frontend": "cross-env NODE_ENV=test ts-mocha --config .mocharc-frontend.yml \"frontend/**/*.spec.@(js|ts|tsx)\"",
"test:e2e": "cypress run",
"test:core": "cross-env NODE_CONFIG_ENV=test mocha \"core/**/*.spec.js\" \"lib/**/*.spec.js\" \"services/**/*.spec.js\"",
"test:package": "mocha \"gh-badges/**/*.spec.js\"",
"test:package": "mocha \"badge-maker/**/*.spec.js\"",
"test:entrypoint": "cross-env NODE_CONFIG_ENV=test mocha entrypoint.spec.js",
"test:integration": "cross-env NODE_CONFIG_ENV=test mocha \"core/**/*.integration.js\" \"services/**/*.integration.js\"",
"test:services": "cross-env NODE_CONFIG_ENV=test mocha --delay core/service-test-runner/cli.js",

View File

@@ -27,7 +27,7 @@ module.exports = class SnykVulnerabilityGitHub extends SynkVulnerabilityBase {
namedParams: {
user: 'badges',
repo: 'shields',
manifestFilePath: 'gh-badges/package.json',
manifestFilePath: 'badge-maker/package.json',
},
staticPreview: this.render({ vulnerabilities: '0' }),
documentation: `

View File

@@ -32,7 +32,7 @@ t.create('valid target manifest path')
})
t.create('invalid target manifest path')
.get('/badges/shields/gh-badges/requirements.txt.json')
.get('/badges/shields/badge-maker/requirements.txt.json')
.timeout(20000)
.expectBadge({
label: 'vulnerabilities',
@@ -66,12 +66,12 @@ t.create('repo has vulnerabilities')
})
t.create('target manifest file has no vulnerabilities')
.get('/badges/shields/gh-badges/package.json.json')
.get('/badges/shields/badge-maker/package.json.json')
.intercept(nock =>
nock('https://snyk.io/test/github/badges/shields')
.get('/badge.svg')
.query({
targetFile: 'gh-badges/package.json',
targetFile: 'badge-maker/package.json',
})
.reply(200, zeroVulnerabilitiesSvg)
)
@@ -82,12 +82,12 @@ t.create('target manifest file has no vulnerabilities')
})
t.create('target manifest file has vulnerabilities')
.get('/badges/shields/gh-badges/package.json.json')
.get('/badges/shields/badge-maker/package.json.json')
.intercept(nock =>
nock('https://snyk.io/test/github/badges/shields')
.get('/badge.svg')
.query({
targetFile: 'gh-badges/package.json',
targetFile: 'badge-maker/package.json',
})
.reply(200, twoVulnerabilitiesSvg)
)