diff --git a/Dockerfile b/Dockerfile index 30d3ef6a0914321ee5f17512ac337a58cb025adb..5f535e56fcfd52f84bcd94165396c80b249f6194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/README.md b/README.md index 7388bf49df8676f014b9d8a99edd95745faada02..41ff58ce5233fab2461b6e1e26d055e4c1b18070 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/__snapshots__/make-badge.spec.js b/__snapshots__/make-badge.spec.js index cfce1a6c5675d5561f2ca1a1669af5d5c6e0deed..43ce0ff6e526ca0ea979b03700572c367218bdf7 100644 --- a/__snapshots__/make-badge.spec.js +++ b/__snapshots__/make-badge.spec.js @@ -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> ` diff --git a/gh-badges/.npmignore b/badge-maker/.npmignore similarity index 100% rename from gh-badges/.npmignore rename to badge-maker/.npmignore diff --git a/gh-badges/CHANGELOG.md b/badge-maker/CHANGELOG.md similarity index 67% rename from gh-badges/CHANGELOG.md rename to badge-maker/CHANGELOG.md index eb3dc7be203593815a359a3a9885ab978842985d..7701d5e4fa47f7bf8a173dce62c5a195ab9f02e4 100644 --- a/gh-badges/CHANGELOG.md +++ b/badge-maker/CHANGELOG.md @@ -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 diff --git a/gh-badges/LICENSE b/badge-maker/LICENSE similarity index 100% rename from gh-badges/LICENSE rename to badge-maker/LICENSE diff --git a/gh-badges/README.md b/badge-maker/README.md similarity index 65% rename from gh-badges/README.md rename to badge-maker/README.md index 048b6e5c2531b6d844be3e12e15dc10b84e451e3..bb523ae554c296d8e9ee84ea77e0bf30cf4754b3 100644 --- a/gh-badges/README.md +++ b/badge-maker/README.md @@ -1,12 +1,12 @@ -# gh-badges +# badge-maker -[](https://npmjs.org/package/gh-badges) -[](https://npmjs.org/package/gh-badges) +[](https://npmjs.org/package/badge-maker) +[](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 - - format: 'svg', // Also supports json - - color: '#4c1', - labelColor: '#555', + label: 'build', // (Optional) Badge label + message: 'passed', // (Required) Badge message + labelColor: '#555', // (Optional) Label color + color: '#4c1', // (Optional) Message color - // 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:-`. diff --git a/gh-badges/lib/badge-cli.js b/badge-maker/lib/badge-cli.js similarity index 50% rename from gh-badges/lib/badge-cli.js rename to badge-maker/lib/badge-cli.js index 58d3a61e2129b826a9c72cda84548073fb566bf7..a930a01e4c867d7b4aba04874817a3abb3f7a79c 100755 --- a/gh-badges/lib/badge-cli.js +++ b/badge-maker/lib/badge-cli.js @@ -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 - } -} - -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) + badgeData.color = color + if (labelColor) { + badgeData.labelColor = labelColor } } -;(async () => { +;(() => { try { - await main() + console.log(makeBadge(badgeData)) } catch (e) { console.error(e) process.exit(1) diff --git a/gh-badges/lib/badge-cli.spec.js b/badge-maker/lib/badge-cli.spec.js similarity index 74% rename from gh-badges/lib/badge-cli.spec.js rename to badge-maker/lib/badge-cli.spec.js index 4c0478264d8968366c9e988427a34c574e57d195..b7c7eac69bc69d16c53f1e2ecc9f216cbd7f4ca0 100644 --- a/gh-badges/lib/badge-cli.spec.js +++ b/badge-maker/lib/badge-cli.spec.js @@ -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) - }) }) diff --git a/badge-maker/lib/badge-renderers.js b/badge-maker/lib/badge-renderers.js new file mode 100644 index 0000000000000000000000000000000000000000..ffa2483c55a9decd91ca6b895c483fcaa6cd0cbf --- /dev/null +++ b/badge-maker/lib/badge-renderers.js @@ -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, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') + } +} + +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 } diff --git a/gh-badges/lib/color.js b/badge-maker/lib/color.js similarity index 94% rename from gh-badges/lib/color.js rename to badge-maker/lib/color.js index fbfa8a13391c9551b4ae5b11fe765b9fa61bf5c9..6273747f369e8902bc8a14ffe3d0e16bf020cce3 100644 --- a/gh-badges/lib/color.js +++ b/badge-maker/lib/color.js @@ -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', diff --git a/gh-badges/lib/color.spec.js b/badge-maker/lib/color.spec.js similarity index 100% rename from gh-badges/lib/color.spec.js rename to badge-maker/lib/color.spec.js diff --git a/badge-maker/lib/index.js b/badge-maker/lib/index.js new file mode 100644 index 0000000000000000000000000000000000000000..49bef6c58aee8cf0acd52518d2c894fcbee79861 --- /dev/null +++ b/badge-maker/lib/index.js @@ -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, +} diff --git a/badge-maker/lib/index.spec.js b/badge-maker/lib/index.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..27ed81591d7b0f86c40f7183d34d54ff46791622 --- /dev/null +++ b/badge-maker/lib/index.spec.js @@ -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)' + ) + }) +}) diff --git a/badge-maker/lib/make-badge.js b/badge-maker/lib/make-badge.js new file mode 100644 index 0000000000000000000000000000000000000000..e9fc223855b2775cd3b65db6d78e60e1ceb0d2c4 --- /dev/null +++ b/badge-maker/lib/make-badge.js @@ -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, + }) +} diff --git a/badge-maker/lib/make-badge.spec.js b/badge-maker/lib/make-badge.spec.js new file mode 100644 index 0000000000000000000000000000000000000000..152f07b006cff01598fbdb7ca40071e73817d8fd --- /dev/null +++ b/badge-maker/lib/make-badge.spec.js @@ -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) + }) + }) +}) diff --git a/gh-badges/package.json b/badge-maker/package.json similarity index 83% rename from gh-badges/package.json rename to badge-maker/package.json index 7ee056666176006d95e4969c8225076d5fd529c3..947bf5bc615d7fc7ebd6f6192a09df65a96a57e0 100644 --- a/gh-badges/package.json +++ b/badge-maker/package.json @@ -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" diff --git a/core/badge-urls/make-badge-url.d.ts b/core/badge-urls/make-badge-url.d.ts index 71f109ee0572038798470c9b036af6a75b911ee4..f2bdb8be5f969fb681e4542e0103091f2492e0ce 100644 --- a/core/badge-urls/make-badge-url.d.ts +++ b/core/badge-urls/make-badge-url.d.ts @@ -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 diff --git a/core/badge-urls/make-badge-url.js b/core/badge-urls/make-badge-url.js index 25c1159b21fb1b608173d5b856676ef1394a63e8..415676589d3a8567412556d0940dd38ea43ad61d 100644 --- a/core/badge-urls/make-badge-url.js +++ b/core/badge-urls/make-badge-url.js @@ -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, }) diff --git a/core/base-service/base-non-memory-caching.js b/core/base-service/base-non-memory-caching.js index d2671f3f56e77ffd4525624f3d67d6975e47f692..f441caf7167a194c78d005589c2072ac96ba01a9 100644 --- a/core/base-service/base-non-memory-caching.js +++ b/core/base-service/base-non-memory-caching.js @@ -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') diff --git a/core/base-service/base-static.js b/core/base-service/base-static.js index c932b317bc9a256e22d19248cf037a9c109fc494..3781d450e3578b3081487ae13868592502887c17 100644 --- a/core/base-service/base-static.js +++ b/core/base-service/base-static.js @@ -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, diff --git a/core/base-service/base-svg-scraping.spec.js b/core/base-service/base-svg-scraping.spec.js index 22154147a32bf86edc5aef0c9f2cfb08b023ee1a..520b7b9a2ff93c5f45f7c575f1452bb4054641a5 100644 --- a/core/base-service/base-svg-scraping.spec.js +++ b/core/base-service/base-svg-scraping.spec.js @@ -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 }) { diff --git a/core/base-service/base.spec.js b/core/base-service/base.spec.js index ae4879630819c355f2abb8591aeaecc6905032ca..01f28d0f5fbc7cb864cd43dbe95fcba625766bdf 100644 --- a/core/base-service/base.spec.js +++ b/core/base-service/base.spec.js @@ -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, diff --git a/core/base-service/coalesce-badge.js b/core/base-service/coalesce-badge.js index a77b8dcc2198faf57532063db3f945c9d34162eb..ba664dfdc981e3eb3a533e752424bcd5908b321e 100644 --- a/core/base-service/coalesce-badge.js +++ b/core/base-service/coalesce-badge.js @@ -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) { diff --git a/core/base-service/coalesce-badge.spec.js b/core/base-service/coalesce-badge.spec.js index 224aec40507c66229f8fbef75755b1e84eaee11e..443fec3db28a195dc1e6b063b7944ad642fea5ea 100644 --- a/core/base-service/coalesce-badge.spec.js +++ b/core/base-service/coalesce-badge.spec.js @@ -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') }) }) diff --git a/core/base-service/legacy-request-handler.js b/core/base-service/legacy-request-handler.js index f14ff236a72e2a6ef6be45d3634706d45237e26a..3d3096a5ecda4826c4f51b3be7a550e1fdbe098e 100644 --- a/core/base-service/legacy-request-handler.js +++ b/core/base-service/legacy-request-handler.js @@ -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, diff --git a/core/server/server.js b/core/server/server.js index d2f2ef8d7c3bfe73efeea4854cca4d595bbe0f12..33fa141ca710d0ff75460b9195676962dda13550 100644 --- a/core/server/server.js +++ b/core/server/server.js @@ -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') diff --git a/doc/code-walkthrough.md b/doc/code-walkthrough.md index f9ea519d7997dfd423160057016935766b4bf2f7..6907ae0348255adb321e6ac18d0f8b7d551d08d3 100644 --- a/doc/code-walkthrough.md +++ b/doc/code-walkthrough.md @@ -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 diff --git a/doc/self-hosting.md b/doc/self-hosting.md index f17a57a88997d08af82e8adfbee13c34765f2157..20e120775b521b32e2ef8ac8f62d5eea80c23d50 100644 --- a/doc/self-hosting.md +++ b/doc/self-hosting.md @@ -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]/ diff --git a/frontend/components/development/style-page.tsx b/frontend/components/development/style-page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..85f0f1f7b583c1ca5ce9b5a1f80c0c15879b5b98 --- /dev/null +++ b/frontend/components/development/style-page.tsx @@ -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> + ) +} diff --git a/gatsby-node.js b/gatsby-node.js index 64dddb967d58e5eaad11849c56801c32e71d9f21..01cedf480d952758da974027571920e20f395f3c 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -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( diff --git a/gh-badges/lib/index.js b/gh-badges/lib/index.js deleted file mode 100644 index 2ec589d6fde1759b424b920f136eb285e36222f4..0000000000000000000000000000000000000000 --- a/gh-badges/lib/index.js +++ /dev/null @@ -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, -} diff --git a/gh-badges/lib/index.spec.js b/gh-badges/lib/index.spec.js deleted file mode 100644 index 68a6e7d3b17ae989675d795c8628a715fb938e68..0000000000000000000000000000000000000000 --- a/gh-badges/lib/index.spec.js +++ /dev/null @@ -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) - }) -}) diff --git a/gh-badges/lib/make-badge.js b/gh-badges/lib/make-badge.js deleted file mode 100644 index 7ae810bbee2e576e718b63d95279280e5bc0f4ca..0000000000000000000000000000000000000000 --- a/gh-badges/lib/make-badge.js +++ /dev/null @@ -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, '&') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/"/g, '"') - .replace(/'/g, ''') - } -} - -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) -} diff --git a/gh-badges/lib/make-badge.spec.js b/gh-badges/lib/make-badge.spec.js deleted file mode 100644 index 56d4f89ddcee2ce1ef27177e9eaf30ed78eb259c..0000000000000000000000000000000000000000 --- a/gh-badges/lib/make-badge.spec.js +++ /dev/null @@ -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) - }) - }) -}) diff --git a/gh-badges/lib/svg-to-img.js b/gh-badges/lib/svg-to-img.js deleted file mode 100644 index a8d11b0ca0a724f2b575b42fd560a14f6c6873f3..0000000000000000000000000000000000000000 --- a/gh-badges/lib/svg-to-img.js +++ /dev/null @@ -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 diff --git a/gh-badges/lib/svg-to-img.spec.js b/gh-badges/lib/svg-to-img.spec.js deleted file mode 100644 index cd46df659620c2d036a19d51cfb33f3119b64a31..0000000000000000000000000000000000000000 --- a/gh-badges/lib/svg-to-img.spec.js +++ /dev/null @@ -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) - }) -}) diff --git a/gh-badges/templates/flat-square-template.svg b/gh-badges/templates/flat-square-template.svg deleted file mode 100644 index 0bf8927e7aa0f8e8a0f32e68bcd99e996a8392de..0000000000000000000000000000000000000000 --- a/gh-badges/templates/flat-square-template.svg +++ /dev/null @@ -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> diff --git a/gh-badges/templates/flat-template.svg b/gh-badges/templates/flat-template.svg deleted file mode 100644 index a98f1b4772acbc618e03613258bf8ec9185a2b93..0000000000000000000000000000000000000000 --- a/gh-badges/templates/flat-template.svg +++ /dev/null @@ -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> diff --git a/gh-badges/templates/for-the-badge-template.svg b/gh-badges/templates/for-the-badge-template.svg deleted file mode 100644 index be72b83a0d54904c2624d644fbc0a9c52bd0f8bc..0000000000000000000000000000000000000000 --- a/gh-badges/templates/for-the-badge-template.svg +++ /dev/null @@ -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> diff --git a/gh-badges/templates/plastic-template.svg b/gh-badges/templates/plastic-template.svg deleted file mode 100644 index b99642f881f2f9a40e74d64d757bde21a18edfb1..0000000000000000000000000000000000000000 --- a/gh-badges/templates/plastic-template.svg +++ /dev/null @@ -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> diff --git a/gh-badges/templates/social-template.svg b/gh-badges/templates/social-template.svg deleted file mode 100644 index 37dd2ff219b4e6e3c74d234b9fdc4bfb69941ee1..0000000000000000000000000000000000000000 --- a/gh-badges/templates/social-template.svg +++ /dev/null @@ -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> diff --git a/lib/logos.js b/lib/logos.js index adbda6c0b6e47651f77dee5b8ba7884f112a22be..d56cd9163c6d1c1afdaebdc42df47513ef0be91c 100644 --- a/lib/logos.js +++ b/lib/logos.js @@ -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')() diff --git a/package-lock.json b/package-lock.json index 35757ce458b8ed055e265b6d2a2e3972ef72dc9a..e21c824c3ed68f4c87e362c053d7cbdb36c2f7da 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 1774a857a3b5acf496b74b2cbcb664dffebebaa9..e64f56ad1d35e377359d3089c47ce68406e32367 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/services/snyk/snyk-vulnerability-github.service.js b/services/snyk/snyk-vulnerability-github.service.js index 878584ae7f1ee13e89e14050c21ed2552d2a895f..ede8a0a503e17d5444fe251b31307cb4239d979f 100644 --- a/services/snyk/snyk-vulnerability-github.service.js +++ b/services/snyk/snyk-vulnerability-github.service.js @@ -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: ` diff --git a/services/snyk/snyk-vulnerability-github.tester.js b/services/snyk/snyk-vulnerability-github.tester.js index ab660eb8e2d0c5d5f63aa8f854cb984a0dd62011..23305db826bb12ca896c17fbfb24ffa4e1a4e7fa 100644 --- a/services/snyk/snyk-vulnerability-github.tester.js +++ b/services/snyk/snyk-vulnerability-github.tester.js @@ -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) )