diff --git a/services/npm/npm-unpacked-size.service.js b/services/npm/npm-unpacked-size.service.js new file mode 100644 index 0000000000000..5dd7d64c54f1a --- /dev/null +++ b/services/npm/npm-unpacked-size.service.js @@ -0,0 +1,87 @@ +import Joi from 'joi' +import prettyBytes from 'pretty-bytes' +import { pathParam, queryParam } from '../index.js' +import { optionalNonNegativeInteger } from '../validators.js' +import NpmBase, { packageNameDescription } from './npm-base.js' + +const schema = Joi.object({ + dist: Joi.object({ + unpackedSize: optionalNonNegativeInteger, + }).required(), +}).required() + +export default class NpmUnpackedSize extends NpmBase { + static category = 'size' + + static route = { + base: 'npm/unpacked-size', + pattern: ':scope(@[^/]+)?/:packageName/:version*', + } + + static openApi = { + '/npm/unpacked-size/{packageName}': { + get: { + summary: 'NPM Unpacked Size', + parameters: [ + pathParam({ + name: 'packageName', + example: 'npm', + description: packageNameDescription, + }), + queryParam({ + name: 'registry_uri', + example: 'https://registry.npmjs.com', + }), + ], + }, + }, + '/npm/unpacked-size/{packageName}/{version}': { + get: { + summary: 'NPM Unpacked Size (with version)', + parameters: [ + pathParam({ + name: 'packageName', + example: 'npm', + description: packageNameDescription, + }), + pathParam({ + name: 'version', + example: '4.18.2', + }), + queryParam({ + name: 'registry_uri', + example: 'https://registry.npmjs.com', + }), + ], + }, + }, + } + + static defaultBadgeData = { label: 'unpacked size' } + + async fetch({ registryUrl, packageName, version }) { + return this._requestJson({ + schema, + url: `${registryUrl}/${packageName}/${version}`, + }) + } + + async handle( + { scope, packageName, version }, + { registry_uri: registryUrl = 'https://registry.npmjs.org' }, + ) { + const packageNameWithScope = scope ? `${scope}/${packageName}` : packageName + const { dist } = await this.fetch({ + registryUrl, + packageName: packageNameWithScope, + version: version ?? 'latest', + }) + const { unpackedSize } = dist + + return { + label: 'unpacked size', + message: unpackedSize ? prettyBytes(unpackedSize) : 'unknown', + color: unpackedSize ? 'blue' : 'lightgray', + } + } +} diff --git a/services/npm/npm-unpacked-size.tester.js b/services/npm/npm-unpacked-size.tester.js new file mode 100644 index 0000000000000..7621b559f4cf2 --- /dev/null +++ b/services/npm/npm-unpacked-size.tester.js @@ -0,0 +1,28 @@ +import { isFileSize } from '../test-validators.js' +import { createServiceTester } from '../tester.js' + +export const t = await createServiceTester() + +t.create('Latest unpacked size') + .get('/firereact.json') + .expectBadge({ label: 'unpacked size', message: isFileSize }) + +t.create('Nonexistent unpacked size with version') + .get('/express/4.16.0.json') + .expectBadge({ label: 'unpacked size', message: 'unknown' }) + +t.create('Unpacked size with version') + .get('/firereact/0.7.0.json') + .expectBadge({ label: 'unpacked size', message: '147 kB' }) + +t.create('Unpacked size for scoped package') + .get('/@testing-library/react.json') + .expectBadge({ label: 'unpacked size', message: isFileSize }) + +t.create('Unpacked size for scoped package with version') + .get('/@testing-library/react/14.2.1.json') + .expectBadge({ label: 'unpacked size', message: '5.41 MB' }) + +t.create('Nonexistent unpacked size for scoped package with version') + .get('/@cycle/rx-run/7.2.0.json') + .expectBadge({ label: 'unpacked size', message: 'unknown' })