Skip to content

Commit

Permalink
Expand response and find the correct release
Browse files Browse the repository at this point in the history
  • Loading branch information
GeoffSelby committed Jun 1, 2021
1 parent c8692bc commit 4d3c20b
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 33 deletions.
62 changes: 62 additions & 0 deletions services/packagist/packagist-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const Joi = require('joi')
const { BaseJsonService } = require('..')
const { isStable, latest } = require('../php-version')

const packageSchema = Joi.array().items(
Joi.object({
Expand Down Expand Up @@ -42,6 +43,30 @@ class BasePackagistService extends BaseJsonService {
})
}

/**
* Fetch dev releases method.
*
* This method utilize composer metadata API which
* "... is the preferred way to access the data as it is always up to date,
* and dumped to static files so it is very efficient on our end." (comment from official documentation).
* For more information please refer to https://packagist.org/apidoc#get-package-data.
*
* @param {object} attrs Refer to individual attrs
* @param {string} attrs.user package user
* @param {string} attrs.repo package repository
* @param {Joi} attrs.schema Joi schema to validate the response transformed to JSON
* @param {string} attrs.server URL for the packagist registry server (Optional)
* @returns {object} Parsed response
*/
async fetchDev({ user, repo, schema, server = 'https://packagist.org' }) {
const url = `${server}/p2/${user.toLowerCase()}/${repo.toLowerCase()}~dev.json`

return this._requestJson({
schema,
url,
})
}

/**
* It is highly recommended to use base fetch method!
*
Expand Down Expand Up @@ -81,6 +106,43 @@ class BasePackagistService extends BaseJsonService {
getPackageName(user, repo) {
return `${user.toLowerCase()}/${repo.toLowerCase()}`
}

decompressResponse(json, packageName) {
const versions = json.packages[packageName]
const expanded = []
let expandedVersion = null

versions.forEach(versionData => {
if (!expandedVersion) {
expandedVersion = versionData
expanded.push(expandedVersion)
}

Object.entries(versionData).forEach(([key, value]) => {
if (value === '__unset') {
delete expandedVersion[key]
} else {
expandedVersion[key] = value
}
})

expandedVersion = { ...expandedVersion }

expanded.push(expandedVersion)
})

return expanded
}

findRelease(json, versions = []) {
json.forEach(version => {
versions.push(version.version)
})

const release = latest(versions.filter(isStable)) || latest(versions)

return json.filter(version => version.version === release)[0]
}
}

const customServerDocumentationFragment = `
Expand Down
7 changes: 6 additions & 1 deletion services/packagist/packagist-license.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const {
const packageSchema = Joi.array()
.items(
Joi.object({
version: Joi.string(),
license: Joi.array(),
}).required()
)
Expand Down Expand Up @@ -59,7 +60,11 @@ module.exports = class PackagistLicense extends BasePackagistService {
transform({ json, user, repo }) {
const packageName = this.getPackageName(user, repo)

const license = json.packages[packageName][0].license
const decompressed = this.decompressResponse(json, packageName)

const version = this.findRelease(decompressed)

const license = version.license

if (!license) {
throw new NotFound({ prettyMessage: 'license not found' })
Expand Down
54 changes: 54 additions & 0 deletions services/packagist/packagist-license.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,60 @@ describe('PackagistLicense', function () {
.that.equals('MIT-latest')
})

it('should return the license of the most recent stable release', function () {
const json = {
packages: {
'frodo/the-one-package': [
{
version: '1.2.4-RC1', // Pre-release
license: 'MIT-latest',
},
{
version: '1.2.3', // Stable release
license: 'MIT',
},
],
},
}

expect(
PackagistLicense.prototype.transform({
json,
user: 'frodo',
repo: 'the-one-package',
})
)
.to.have.property('license')
.that.equals('MIT')
})

it('should return the license of the most recent pre-release if no stable releases', function () {
const json = {
packages: {
'frodo/the-one-package': [
{
version: '1.2.4-RC2',
license: 'MIT-latest',
},
{
version: '1.2.4-RC1',
license: 'MIT',
},
],
},
}

expect(
PackagistLicense.prototype.transform({
json,
user: 'frodo',
repo: 'the-one-package',
})
)
.to.have.property('license')
.that.equals('MIT-latest')
})

it('should throw NotFound when license key not in response', function () {
const json = {
packages: {
Expand Down
68 changes: 56 additions & 12 deletions services/packagist/packagist-php-version.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,25 +68,68 @@ module.exports = class PackagistPhpVersion extends BasePackagistService {
}
}

findVersionIndex(json, user, repo, version) {
const packageArr = json.packages[this.getPackageName(user, repo)]
findVersionIndex(json, version) {
return json.findIndex(v => v.version === version)
}

async findSpecifiedVersion(json, user, repo, version, server) {
let release

if ((release = json[this.findVersionIndex(json, version)])) {
return release
} else {
try {
const allData = await this.fetchDev({
user,
repo,
schema: allVersionsSchema,
server,
})

const decompressed = this.decompressResponse(
allData,
this.getPackageName(user, repo)
)

return packageArr.findIndex(v => v.version === version)
return decompressed[this.findVersionIndex(decompressed, version)]
} catch (e) {
return release
}
}
}

transform({ json, user, repo, version = '' }) {
const packageVersion =
version === ''
? json.packages[this.getPackageName(user, repo)][0]
: json.packages[this.getPackageName(user, repo)][
this.findVersionIndex(json, user, repo, version)
]
async transform({ json, user, repo, version = '', server }) {
let packageVersion
const decompressed = this.decompressResponse(
json,
this.getPackageName(user, repo)
)

if (version === '') {
packageVersion = this.findRelease(decompressed)
} else {
try {
packageVersion = await this.findSpecifiedVersion(
decompressed,
user,
repo,
version,
server
)
} catch {
packageVersion = null
}
}

if (!packageVersion) {
throw new NotFound({ prettyMessage: 'invalid version' })
}

if (!packageVersion.require || !packageVersion.require.php) {
if (
!packageVersion.require ||
!packageVersion.require.php ||
packageVersion.require.php === '__unset'
) {
throw new NotFound({ prettyMessage: 'version requirement not found' })
}

Expand All @@ -100,11 +143,12 @@ module.exports = class PackagistPhpVersion extends BasePackagistService {
schema: allVersionsSchema,
server,
})
const { phpVersion } = this.transform({
const { phpVersion } = await this.transform({
json: allData,
user,
repo,
version,
server,
})
return this.constructor.render({ php: phpVersion })
}
Expand Down
34 changes: 14 additions & 20 deletions services/packagist/packagist-php-version.spec.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
'use strict'

const { expect } = require('chai')
const { NotFound } = require('..')
const PackagistPhpVersion = require('./packagist-php-version.service')

describe('PackagistPhpVersion', function () {
Expand All @@ -24,20 +23,18 @@ describe('PackagistPhpVersion', function () {
},
}

it('should throw NotFound when package version is missing', function () {
expect(() =>
it('should throw NotFound when package version is missing', async function () {
await expect(
PackagistPhpVersion.prototype.transform({
json,
user: 'frodo',
repo: 'the-one-package',
version: '4.0.0',
})
)
.to.throw(NotFound)
.with.property('prettyMessage', 'invalid version')
).to.be.rejectedWith('invalid version')
})

it('should throw NotFound when PHP version not found on package when using default release', function () {
it('should throw NotFound when PHP version not found on package when using default release', async function () {
const specJson = {
packages: {
'frodo/the-one-package': [
Expand All @@ -55,18 +52,16 @@ describe('PackagistPhpVersion', function () {
],
},
}
expect(() =>
await expect(
PackagistPhpVersion.prototype.transform({
json: specJson,
user: 'frodo',
repo: 'the-one-package',
})
)
.to.throw(NotFound)
.with.property('prettyMessage', 'version requirement not found')
).to.be.rejectedWith('version requirement not found')
})

it('should throw NotFound when PHP version not found on package when using specified release', function () {
it('should throw NotFound when PHP version not found on package when using specified release', async function () {
const specJson = {
packages: {
'frodo/the-one-package': [
Expand All @@ -80,25 +75,24 @@ describe('PackagistPhpVersion', function () {
},
{
version: '1.0.0',
require: { php: '__unset' },
},
],
},
}
expect(() =>
await expect(
PackagistPhpVersion.prototype.transform({
json: specJson,
user: 'frodo',
repo: 'the-one-package',
version: '1.0.0',
})
)
.to.throw(NotFound)
.with.property('prettyMessage', 'version requirement not found')
).to.be.rejectedWith('version requirement not found')
})

it('should return PHP version for the default release', function () {
it('should return PHP version for the default release', async function () {
expect(
PackagistPhpVersion.prototype.transform({
await PackagistPhpVersion.prototype.transform({
json,
user: 'frodo',
repo: 'the-one-package',
Expand All @@ -108,9 +102,9 @@ describe('PackagistPhpVersion', function () {
.that.equals('^7.4 || 8')
})

it('should return PHP version for the specified release', function () {
it('should return PHP version for the specified release', async function () {
expect(
PackagistPhpVersion.prototype.transform({
await PackagistPhpVersion.prototype.transform({
json,
user: 'frodo',
repo: 'the-one-package',
Expand Down

0 comments on commit 4d3c20b

Please sign in to comment.