Skip to content

Commit

Permalink
reduce overhead of NPM Last Update badge; test [npm] (#10666)
Browse files Browse the repository at this point in the history
* reduce overhead of [NpmLastUpdate] badge

* use buildRoute for version without tag
  • Loading branch information
chris48s authored Nov 13, 2024
1 parent 2bd926e commit cbb7ab5
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 55 deletions.
26 changes: 21 additions & 5 deletions services/npm/npm-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,11 @@ export default class NpmBase extends BaseJsonService {
}

async _requestJson(data) {
return super._requestJson(
this.authHelper.withBearerAuthHeader({
let payload
if (data?.options?.headers?.Accept) {
payload = data
} else {
payload = {
...data,
options: {
headers: {
Expand All @@ -91,8 +94,9 @@ export default class NpmBase extends BaseJsonService {
Accept: '*/*',
},
},
}),
)
}
}
return super._requestJson(this.authHelper.withBearerAuthHeader(payload))
}

async fetchPackageData({ registryUrl, scope, packageName, tag }) {
Expand Down Expand Up @@ -144,7 +148,13 @@ export default class NpmBase extends BaseJsonService {
return this.constructor._validate(packageData, packageDataSchema)
}

async fetch({ registryUrl, scope, packageName, schema }) {
async fetch({
registryUrl,
scope,
packageName,
schema,
abbreviated = false,
}) {
registryUrl = registryUrl || this.constructor.defaultRegistryUrl
let url

Expand All @@ -158,9 +168,15 @@ export default class NpmBase extends BaseJsonService {
url = `${registryUrl}/${scoped}`
}

// https://github.com/npm/registry/blob/main/docs/responses/package-metadata.md
const options = abbreviated
? { headers: { Accept: 'application/vnd.npm.install-v1+json' } }
: {}

return this._requestJson({
url,
schema,
options,
httpErrors: { 404: 'package not found' },
})
}
Expand Down
118 changes: 75 additions & 43 deletions services/npm/npm-last-update.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,55 +3,107 @@ import dayjs from 'dayjs'
import { InvalidResponse, NotFound, pathParam, queryParam } from '../index.js'
import { formatDate } from '../text-formatters.js'
import { age as ageColor } from '../color-formatters.js'
import NpmBase, { packageNameDescription } from './npm-base.js'
import NpmBase, {
packageNameDescription,
queryParamSchema,
} from './npm-base.js'

const updateResponseSchema = Joi.object({
time: Joi.object({
created: Joi.string().required(),
modified: Joi.string().required(),
})
const fullSchema = Joi.object({
time: Joi.object()
.pattern(Joi.string().required(), Joi.string().required())
.required(),
'dist-tags': Joi.object()
.pattern(Joi.string().required(), Joi.string().required())
.required(),
}).required()

export class NpmLastUpdate extends NpmBase {
const abbreviatedSchema = Joi.object({
modified: Joi.string().required(),
}).required()

class NpmLastUpdateBase extends NpmBase {
static category = 'activity'

static route = this.buildRoute('npm/last-update', { withTag: true })
static defaultBadgeData = { label: 'last updated' }

static render({ date }) {
return {
message: formatDate(date),
color: ageColor(date),
}
}
}

export class NpmLastUpdateWithTag extends NpmLastUpdateBase {
static route = {
base: 'npm/last-update',
pattern: ':scope(@[^/]+)?/:packageName/:tag',
queryParamSchema,
}

static openApi = {
'/npm/last-update/{packageName}': {
'/npm/last-update/{packageName}/{tag}': {
get: {
summary: 'NPM Last Update',
summary: 'NPM Last Update (with dist tag)',
parameters: [
pathParam({
name: 'packageName',
example: 'verdaccio',
packageNameDescription,
}),
pathParam({
name: 'tag',
example: 'next-8',
}),
queryParam({
name: 'registry_uri',
example: 'https://registry.npmjs.com',
}),
],
},
},
'/npm/last-update/{packageName}/{tag}': {
}

async handle(namedParams, queryParams) {
const { scope, packageName, tag, registryUrl } =
this.constructor.unpackParams(namedParams, queryParams)

const packageData = await this.fetch({
registryUrl,
scope,
packageName,
schema: fullSchema,
})

const tagVersion = packageData['dist-tags'][tag]

if (!tagVersion) {
throw new NotFound({ prettyMessage: 'tag not found' })
}

const date = dayjs(packageData.time[tagVersion])

if (!date.isValid) {
throw new InvalidResponse({ prettyMessage: 'invalid date' })
}

return this.constructor.render({ date })
}
}

export class NpmLastUpdate extends NpmLastUpdateBase {
static route = this.buildRoute('npm/last-update', { withTag: false })

static openApi = {
'/npm/last-update/{packageName}': {
get: {
summary: 'NPM Last Update (with dist tag)',
summary: 'NPM Last Update',
parameters: [
pathParam({
name: 'packageName',
example: 'verdaccio',
packageNameDescription,
}),
pathParam({
name: 'tag',
example: 'next-8',
}),
queryParam({
name: 'registry_uri',
example: 'https://registry.npmjs.com',
Expand All @@ -61,41 +113,21 @@ export class NpmLastUpdate extends NpmBase {
},
}

static defaultBadgeData = { label: 'last updated' }

static render({ date }) {
return {
message: formatDate(date),
color: ageColor(date),
}
}

async handle(namedParams, queryParams) {
const { scope, packageName, tag, registryUrl } =
this.constructor.unpackParams(namedParams, queryParams)
const { scope, packageName, registryUrl } = this.constructor.unpackParams(
namedParams,
queryParams,
)

const packageData = await this.fetch({
registryUrl,
scope,
packageName,
schema: updateResponseSchema,
schema: abbreviatedSchema,
abbreviated: true,
})

let date

if (tag) {
const tagVersion = packageData['dist-tags'][tag]

if (!tagVersion) {
throw new NotFound({ prettyMessage: 'tag not found' })
}

date = dayjs(packageData.time[tagVersion])
} else {
const timeKey = packageData.time.modified ? 'modified' : 'created'

date = dayjs(packageData.time[timeKey])
}
const date = dayjs(packageData.modified)

if (!date.isValid) {
throw new InvalidResponse({ prettyMessage: 'invalid date' })
Expand Down
42 changes: 35 additions & 7 deletions services/npm/npm-last-update.tester.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,51 +3,79 @@ import { createServiceTester } from '../tester.js'

export const t = await createServiceTester()

t.create('last updated date (valid package)')
t.create('last updated date, no tag, valid package')
.get('/verdaccio.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last updated date (invalid package)')
t.create('last updated date, no tag, invalid package')
.get('/not-a-package.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})

t.create('last update from custom repository (valid scenario)')
t.create('last updated date, no tag, custom repository, valid package')
.get('/verdaccio.json?registry_uri=https://registry.npmjs.com')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last update scoped package (valid scenario)')
t.create('last updated date, no tag, valid package with scope')
.get('/@npm/types.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last update scoped package (invalid scenario)')
t.create('last updated date, no tag, invalid package with scope')
.get('/@not-a-scoped-package/not-a-valid-package.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})

t.create('last updated date with tag (valid scenario)')
t.create('last updated date, with tag, valid package')
.get('/verdaccio/latest.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last updated date (invalid tag)')
t.create('last updated date, with tag, invalid package')
.get('/not-a-package/doesnt-matter.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})

t.create('last updated date, with tag, invalid tag')
.get('/verdaccio/not-a-valid-tag.json')
.expectBadge({
label: 'last updated',
message: 'tag not found',
})

t.create('last updated date, with tag, custom repository, valid package')
.get('/verdaccio/latest.json?registry_uri=https://registry.npmjs.com')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last updated date, with tag, valid package with scope')
.get('/@npm/types/latest.json')
.expectBadge({
label: 'last updated',
message: isFormattedDate,
})

t.create('last updated date, with tag, invalid package with scope')
.get('/@not-a-scoped-package/not-a-valid-package/doesnt-matter.json')
.expectBadge({
label: 'last updated',
message: 'package not found',
})

0 comments on commit cbb7ab5

Please sign in to comment.