From 99a1efb06f1cde19215f2a85d71c2dae9aef2912 Mon Sep 17 00:00:00 2001 From: Luca Scalzotto Date: Fri, 11 Aug 2023 15:23:39 +0200 Subject: [PATCH] Add support for global and npx install modes (#11) --- site/index.html | 22 +++++++++++++++++++++ site/make-badge.ts | 40 +++++++++++++++++++++++++++++--------- site/style.scss | 7 +++++-- src/badge/badgeElements.ts | 18 +++++++++++++++-- src/badge/drawBadge.ts | 8 ++++++-- src/index.ts | 4 +++- src/installMode.ts | 9 +++++++++ 7 files changed, 92 insertions(+), 16 deletions(-) create mode 100644 src/installMode.ts diff --git a/site/index.html b/site/index.html index 95cba83..10b6b5a 100644 --- a/site/index.html +++ b/site/index.html @@ -35,6 +35,23 @@

Examples

npm badge for Eleventy +
+
+ + https://npmbadge.com/npm/@nestjs/cli?mode=global + + npm badge for Nest CLI +
+
+ + https://npmbadge.com/npm/serve?mode=npx + + npm badge for Serve +
+

@@ -42,6 +59,11 @@

Make Me One!

+
diff --git a/site/make-badge.ts b/site/make-badge.ts index cfafc78..086469c 100644 --- a/site/make-badge.ts +++ b/site/make-badge.ts @@ -3,6 +3,9 @@ const DEBOUNCE_TIME = 300; document.addEventListener('DOMContentLoaded', () => { const input = document.getElementById('make-badge-input') as HTMLInputElement; + const modeSelect = document.getElementById( + 'make-badge-mode' + ) as HTMLSelectElement; const imgOutput = document.getElementById( 'make-badge-img' ) as HTMLImageElement; @@ -14,26 +17,43 @@ document.addEventListener('DOMContentLoaded', () => { 'make-badge-embed-html' ) as HTMLTextAreaElement; - input.addEventListener( - 'input', - debounce(() => setBadgeSrc(input, imgOutput)) - ); - input.addEventListener('input', () => - setStatusAndEmbedCodes(input.value, info, embedMd, embedHtml) + const setBadgeSrcDebounced = debounce(() => + setBadgeSrc(input, modeSelect, imgOutput) ); + const setStatusInstant = () => + setStatusAndEmbedCodes( + input.value, + modeSelect.value, + info, + embedMd, + embedHtml + ); + + input.addEventListener('input', setBadgeSrcDebounced); + input.addEventListener('input', setStatusInstant); + modeSelect.addEventListener('change', setBadgeSrcDebounced); + modeSelect.addEventListener('change', setStatusInstant); + imgOutput.addEventListener('error', () => handleImgError(info)); imgOutput.addEventListener('load', () => handleImgLoad(info)); - setStatusAndEmbedCodes('', info, embedMd, embedHtml); + setStatusAndEmbedCodes('', 'install', info, embedMd, embedHtml); }); function setBadgeSrc( input: HTMLInputElement, + modeSelect: HTMLSelectElement, imgOutput: HTMLImageElement ): void { const pkg = input.value; + const mode = modeSelect.value; + if (pkg) { - imgOutput.src = `${NPM_BADGE_URL}${pkg}`; + let src = `${NPM_BADGE_URL}${pkg}`; + if (mode !== 'install') { + src = `${src}?mode=${mode}`; + } + imgOutput.src = src; } } @@ -47,6 +67,7 @@ function handleImgLoad(info: HTMLSpanElement): void { function setStatusAndEmbedCodes( name: string, + mode: string, info: HTMLSpanElement, embedMd: HTMLTextAreaElement, embedHtml: HTMLTextAreaElement @@ -57,8 +78,9 @@ function setStatusAndEmbedCodes( } else { info.textContent = 'Loading...'; } + const modeQuery = mode === 'install' ? '' : `?mode=${mode}`; - const badgeLink = `${NPM_BADGE_URL}${name}`; + const badgeLink = `${NPM_BADGE_URL}${name}${modeQuery}`; const npmLink = `https://www.npmjs.com/package/${name}`; embedMd.value = `[![npm](${badgeLink})](${npmLink})`; diff --git a/site/style.scss b/site/style.scss index a74c118..57fb06b 100644 --- a/site/style.scss +++ b/site/style.scss @@ -37,7 +37,8 @@ a { } input, -textarea { +textarea, +select { font-size: inherit; color: inherit; padding: 8px; @@ -63,6 +64,7 @@ main { display: flex; justify-content: space-around; gap: 32px; + margin-bottom: 32px; @media screen and (max-width: 1024px) { flex-direction: column; @@ -89,7 +91,8 @@ main { .input-row { margin-bottom: 16px; - input { + input, + select { margin-right: 8px; } diff --git a/src/badge/badgeElements.ts b/src/badge/badgeElements.ts index d29be3a..b61bc3d 100644 --- a/src/badge/badgeElements.ts +++ b/src/badge/badgeElements.ts @@ -3,6 +3,7 @@ import { formatNumber } from '../utils/formatNumber'; import { COLORS } from './colors'; import { timeAgo } from '../utils/timeAgo'; import { FONTS } from './fonts'; +import { InstallMode } from '../installMode'; export interface BadgeElements { npmLogo: TextElement; @@ -19,8 +20,11 @@ export interface TextElement { color: string; } -export function getBadgeElements(pkg: PackageInfo): BadgeElements { - return { +export function getBadgeElements( + pkg: PackageInfo, + installMode?: InstallMode +): BadgeElements { + const result: BadgeElements = { npmLogo: { text: 'npm', font: FONTS.npm, @@ -56,4 +60,14 @@ export function getBadgeElements(pkg: PackageInfo): BadgeElements { color: COLORS.darkGrey, }, }; + + if (installMode === 'global') { + result.installCommand.text = `npm install -g ${pkg.name}`; + } + + if (installMode === 'npx') { + result.installCommand.text = `npx ${pkg.name}`; + } + + return result; } diff --git a/src/badge/drawBadge.ts b/src/badge/drawBadge.ts index d8f1763..cca5222 100644 --- a/src/badge/drawBadge.ts +++ b/src/badge/drawBadge.ts @@ -4,6 +4,7 @@ import { initFonts } from './fonts'; import { getBadgeElements } from './badgeElements'; import { drawText, getTextSizes } from './text'; import { COLORS } from './colors'; +import { InstallMode } from '../installMode'; const BORDER_WIDTH = 2; const PADDING = 9; @@ -23,12 +24,15 @@ initFonts(); | M 1,000 weekly downloads updated 5 months ago | +---------------------------------------------------+ */ -export function drawBadge(pkg: PackageInfo): PNGStream { +export function drawBadge( + pkg: PackageInfo, + installMode?: InstallMode +): PNGStream { const canvas = new Canvas(0, 0); const ctx = canvas.getContext('2d'); ctx.antialias = 'subpixel'; - const elems = getBadgeElements(pkg); + const elems = getBadgeElements(pkg, installMode); const sizes = getTextSizes(ctx, elems); const pkgInfoX = NPM_LOGO_X + sizes.npmLogo.width + LOGO_SPACING_RIGHT; diff --git a/src/index.ts b/src/index.ts index 55e67ff..77c4d0b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ import express from 'express'; import { getPackageInfo } from './packageInfo'; import { drawBadge } from './badge/drawBadge'; import { packageNameMiddleware } from './packageNameMiddleware'; +import { parseInstallMode } from './installMode'; const app = express(); app.use(express.static('_site')); @@ -9,8 +10,9 @@ app.use(express.static('_site')); app.get('/npm/*', packageNameMiddleware, async function (req, res, next) { getPackageInfo(req.packageName) .then((pkg) => { + const { mode } = req.query; res.type('image/png'); - drawBadge(pkg).pipe(res); + drawBadge(pkg, parseInstallMode(mode)).pipe(res); }) .catch(next); }); diff --git a/src/installMode.ts b/src/installMode.ts new file mode 100644 index 0000000..281c1c5 --- /dev/null +++ b/src/installMode.ts @@ -0,0 +1,9 @@ +export type InstallMode = 'install' | 'global' | 'npx'; + +const validModes: InstallMode[] = ['install', 'global', 'npx']; + +export function parseInstallMode(mode?: unknown): InstallMode | undefined { + if (typeof mode === 'string' && validModes.includes(mode as InstallMode)) { + return mode as InstallMode; + } +}