feat(nuget): support changelogs

Refactors nuget lookups to use generatic logic and fill in repositoryUrl to enable changelogs.
This commit is contained in:
Rhys Arkins 2018-06-14 15:52:37 +02:00
parent 8463ba8dad
commit 124807974a
9 changed files with 285 additions and 90 deletions

View file

@ -2,12 +2,14 @@ const { parse } = require('../util/purl');
const github = require('./github'); const github = require('./github');
const npm = require('./npm'); const npm = require('./npm');
const nuget = require('./nuget');
const packagist = require('./packagist'); const packagist = require('./packagist');
const pypi = require('./pypi'); const pypi = require('./pypi');
const datasources = { const datasources = {
github, github,
npm, npm,
nuget,
packagist, packagist,
pypi, pypi,
}; };

View file

@ -1,52 +1,55 @@
const got = require('got'); const got = require('got');
const xmlParser = require('fast-xml-parser'); const xmlParser = require('fast-xml-parser');
const { isVersion, sortVersions } = require('../versioning/semver');
const parse = require('github-url-from-git');
module.exports = { module.exports = {
getVersions, getDependency,
getNuspec,
}; };
const map = new Map(); async function getDependency(purl) {
const headers = {}; const { fullname: name } = purl;
logger.trace(`nuget.getDependency(${name})`);
async function getVersions(name, retries = 5) { const pkgUrl = `https://api.nuget.org/v3-flatcontainer/${name.toLowerCase()}/index.json`;
logger.trace(`getVersions(${name})`);
const url = `https://api.nuget.org/v3-flatcontainer/${name.toLowerCase()}/index.json`;
try { try {
const result = (await got(url, { const res = (await got(pkgUrl, {
cache: process.env.RENOVATE_SKIP_CACHE ? undefined : map,
json: true, json: true,
retries, retries: 5,
headers,
})).body; })).body;
const dep = {
return result.versions; name,
};
dep.releases = res.versions
.filter(isVersion)
.sort(sortVersions)
.map(version => ({ version }));
// look up nuspec for latest release to get repository
const url = `https://api.nuget.org/v3-flatcontainer/${name.toLowerCase()}/${res.versions.pop()}/${name.toLowerCase()}.nuspec`;
try {
const result = await got(url);
const nuspec = xmlParser.parse(result.body, { ignoreAttributes: false });
if (nuspec) {
const repositoryUrl = parse(
nuspec.package.metadata.repository['@_url']
);
if (repositoryUrl) {
dep.repositoryUrl = repositoryUrl;
}
}
} catch (err) /* istanbul ignore next */ {
logger.debug({ depName: name }, 'Error looking up nuspec');
}
logger.trace({ dep }, 'dep');
return dep;
} catch (err) { } catch (err) {
logger.warn({ err, name }, 'nuget getVersions failures: Unknown error'); if (err.statusCode === 404 || err.code === 'ENOTFOUND') {
return null; logger.info({ name }, `Dependency lookup failure: not found`);
} logger.debug({
} err,
});
async function getNuspec(name, version, retries = 5) { return null;
logger.trace(`getNuspec(${name} - ${version})`); }
logger.warn({ err, name }, 'nuget registry failure: Unknown error');
const url = `https://api.nuget.org/v3-flatcontainer/${name.toLowerCase()}/${version}/${name.toLowerCase()}.nuspec`;
try {
const result = await got(url, {
cache: process.env.RENOVATE_SKIP_CACHE ? undefined : map,
json: false,
retries,
headers,
});
const nuspec = xmlParser.parse(result.body, { ignoreAttributes: false });
return nuspec.package;
} catch (err) {
logger.warn({ err, name }, 'nuget getNuspec failures: Unknown error');
return null; return null;
} }
} }

View file

@ -1,3 +1,5 @@
const { isVersion } = require('../../versioning')('semver');
module.exports = { module.exports = {
extractDependencies, extractDependencies,
}; };
@ -13,14 +15,19 @@ function extractDependencies(content) {
); );
if (match) { if (match) {
const depName = match[1]; const depName = match[1];
const currentVersion = match[2]; const currentValue = match[2];
const dep = {
deps.push({
depType: 'nuget', depType: 'nuget',
depName, depName,
currentVersion, currentValue,
lineNumber, lineNumber,
}); purl: 'pkg:nuget/' + depName,
versionScheme: 'semver',
};
if (!isVersion(currentValue)) {
dep.skipReason = 'not-version';
}
deps.push(dep);
} }
lineNumber += 1; lineNumber += 1;
} }

View file

@ -45,7 +45,7 @@ async function lookupUpdates(config) {
// istanbul ignore if // istanbul ignore if
if (allVersions.length === 0) { if (allVersions.length === 0) {
const message = `No versions returned from registry for this package`; const message = `No versions returned from registry for this package`;
logger.warn({ dependency }, message); logger.warn({ dependency: depName, result: dependency }, message);
// TODO: return an object // TODO: return an object
updates.push([ updates.push([
{ {

View file

@ -0,0 +1,44 @@
{
"versions": [
"2.5.7.10213",
"2.5.9.10348",
"2.5.10.11092",
"2.6.0.12051",
"2.6.0.12054",
"2.6.1",
"2.6.2",
"2.6.3",
"2.6.4",
"2.6.5",
"2.6.6",
"3.0.0-alpha",
"3.0.0-alpha-2",
"3.0.0-alpha-3",
"3.0.0-alpha-4",
"3.0.0-alpha-5",
"3.0.0-beta-1",
"3.0.0-beta-2",
"3.0.0-beta-3",
"3.0.0-beta-4",
"3.0.0-beta-5",
"3.0.0-rc",
"3.0.0-rc-2",
"3.0.0-rc-3",
"3.0.0",
"3.0.1",
"3.2.0",
"3.2.1",
"3.4.0",
"3.4.1",
"3.5.0",
"3.6.0",
"3.6.1",
"3.7.0",
"3.7.1",
"3.8.0",
"3.8.1",
"3.9.0",
"3.10.0",
"3.10.1"
]
}

View file

@ -21,7 +21,7 @@
<PackageReference Include="Serilog" Version="2.4.0" /> <PackageReference Include="Serilog" Version="2.4.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" /> <PackageReference Include="Serilog.Extensions.Logging" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Literate" Version="2.1.0" /> <PackageReference Include="Serilog.Sinks.Literate" Version="2.1.0" />
<PackageReference Include="Stateless" Version="3.1.0" /> <PackageReference Include="Stateless" Version="3.1.0.5" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" /> <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="1.0.0" />

View file

@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`datasource/nuget getDependency processes real data 1`] = `
Object {
"name": "nunit",
"releases": Array [
Object {
"version": "2.6.1",
},
Object {
"version": "2.6.2",
},
Object {
"version": "2.6.3",
},
Object {
"version": "2.6.4",
},
Object {
"version": "2.6.5",
},
Object {
"version": "2.6.6",
},
Object {
"version": "3.0.0-alpha",
},
Object {
"version": "3.0.0-alpha-2",
},
Object {
"version": "3.0.0-alpha-3",
},
Object {
"version": "3.0.0-alpha-4",
},
Object {
"version": "3.0.0-alpha-5",
},
Object {
"version": "3.0.0-beta-1",
},
Object {
"version": "3.0.0-beta-2",
},
Object {
"version": "3.0.0-beta-3",
},
Object {
"version": "3.0.0-beta-4",
},
Object {
"version": "3.0.0-beta-5",
},
Object {
"version": "3.0.0-rc",
},
Object {
"version": "3.0.0-rc-2",
},
Object {
"version": "3.0.0-rc-3",
},
Object {
"version": "3.0.0",
},
Object {
"version": "3.0.1",
},
Object {
"version": "3.2.0",
},
Object {
"version": "3.2.1",
},
Object {
"version": "3.4.0",
},
Object {
"version": "3.4.1",
},
Object {
"version": "3.5.0",
},
Object {
"version": "3.6.0",
},
Object {
"version": "3.6.1",
},
Object {
"version": "3.7.0",
},
Object {
"version": "3.7.1",
},
Object {
"version": "3.8.0",
},
Object {
"version": "3.8.1",
},
Object {
"version": "3.9.0",
},
Object {
"version": "3.10.0",
},
Object {
"version": "3.10.1",
},
],
"repositoryUrl": "https://github.com/JamesNK/Newtonsoft.Json",
}
`;

View file

@ -1,44 +1,43 @@
const fs = require('fs'); const fs = require('fs');
const nuget = require('../../lib/datasource/nuget'); const datasource = require('../../lib/datasource');
const got = require('got'); const got = require('got');
const withRepositoryInNuspec = fs.readFileSync(
'test/_fixtures/nuget/sample.nuspec',
'utf8'
);
jest.mock('got'); jest.mock('got');
describe('api/nuget', () => { const res1 = fs.readFileSync('test/_fixtures/nuget/nunit.json', 'utf8');
describe('getVersions', () => { const res2 = fs.readFileSync('test/_fixtures/nuget/sample.nuspec', 'utf8');
it('returns null if errored', async () => {
got.mockReturnValueOnce({});
const nuspec = await nuget.getVersions('MyPackage');
expect(nuspec).toBe(null);
});
it('returns versions list', async () => {
got.mockReturnValueOnce({
body: { versions: ['1.0.0', '2.0.0', '2.1.0', '2.1.1-alpha'] },
});
const versions = await nuget.getVersions('MyPackage');
expect(versions).toHaveLength(4);
});
});
describe('getNuspec', () => { describe('datasource/nuget', () => {
it('returns null if errored', async () => { describe('getDependency', () => {
it('returns null for empty result', async () => {
got.mockReturnValueOnce({}); got.mockReturnValueOnce({});
const nuspec = await nuget.getNuspec('MyPackage', '1.0.0.0'); expect(await datasource.getDependency('pkg:nuget/something')).toBeNull();
expect(nuspec).toBe(null);
}); });
it('returns json-ified nuspec with attributes', async () => { it('returns null for 404', async () => {
got.mockReturnValueOnce({ headers: {}, body: withRepositoryInNuspec }); got.mockImplementationOnce(() =>
const nuspec = await nuget.getNuspec('MyPackage', '1.0.0.0'); Promise.reject({
statusCode: 404,
expect(nuspec.metadata.id).toBe('Newtonsoft.Json'); })
expect(nuspec.metadata.version).toBe('11.0.2');
expect(nuspec.metadata.repository['@_url']).toBe(
'https://github.com/JamesNK/Newtonsoft.Json.git'
); );
expect(await datasource.getDependency('pkg:nuget/something')).toBeNull();
});
it('returns null for unknown error', async () => {
got.mockImplementationOnce(() => {
throw new Error();
});
expect(await datasource.getDependency('pkg:nuget/something')).toBeNull();
});
it('processes real data', async () => {
got.mockReturnValueOnce({
body: JSON.parse(res1),
});
got.mockReturnValueOnce({
body: res2,
});
const res = await datasource.getDependency('pkg:nuget/nunit');
expect(res).not.toBeNull();
expect(res).toMatchSnapshot();
expect(res.repositoryUrl).toBeDefined();
}); });
}); });
}); });

View file

@ -3,76 +3,101 @@
exports[`lib/manager/nuget/extract extractDependencies() extracts all dependencies 1`] = ` exports[`lib/manager/nuget/extract extractDependencies() extracts all dependencies 1`] = `
Array [ Array [
Object { Object {
"currentVersion": "4.5.0", "currentValue": "4.5.0",
"depName": "Autofac", "depName": "Autofac",
"depType": "nuget", "depType": "nuget",
"lineNumber": 12, "lineNumber": 12,
"purl": "pkg:nuget/Autofac",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "4.1.0", "currentValue": "4.1.0",
"depName": "Autofac.Extensions.DependencyInjection", "depName": "Autofac.Extensions.DependencyInjection",
"depType": "nuget", "depType": "nuget",
"lineNumber": 13, "lineNumber": 13,
"purl": "pkg:nuget/Autofac.Extensions.DependencyInjection",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.1.2", "currentValue": "1.1.2",
"depName": "Microsoft.AspNetCore.Hosting", "depName": "Microsoft.AspNetCore.Hosting",
"depType": "nuget", "depType": "nuget",
"lineNumber": 14, "lineNumber": 14,
"purl": "pkg:nuget/Microsoft.AspNetCore.Hosting",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.1.3", "currentValue": "1.1.3",
"depName": "Microsoft.AspNetCore.Mvc.Core", "depName": "Microsoft.AspNetCore.Mvc.Core",
"depType": "nuget", "depType": "nuget",
"lineNumber": 15, "lineNumber": 15,
"purl": "pkg:nuget/Microsoft.AspNetCore.Mvc.Core",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.1.2", "currentValue": "1.1.2",
"depName": "Microsoft.AspNetCore.Server.Kestrel", "depName": "Microsoft.AspNetCore.Server.Kestrel",
"depType": "nuget", "depType": "nuget",
"lineNumber": 16, "lineNumber": 16,
"purl": "pkg:nuget/Microsoft.AspNetCore.Server.Kestrel",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.1.2", "currentValue": "1.1.2",
"depName": "Microsoft.Extensions.Configuration.Json", "depName": "Microsoft.Extensions.Configuration.Json",
"depType": "nuget", "depType": "nuget",
"lineNumber": 17, "lineNumber": 17,
"purl": "pkg:nuget/Microsoft.Extensions.Configuration.Json",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.1.2", "currentValue": "1.1.2",
"depName": "Microsoft.Extensions.Logging.Debug", "depName": "Microsoft.Extensions.Logging.Debug",
"depType": "nuget", "depType": "nuget",
"lineNumber": 18, "lineNumber": 18,
"purl": "pkg:nuget/Microsoft.Extensions.Logging.Debug",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "10.0.2", "currentValue": "10.0.2",
"depName": "Newtonsoft.Json", "depName": "Newtonsoft.Json",
"depType": "nuget", "depType": "nuget",
"lineNumber": 19, "lineNumber": 19,
"purl": "pkg:nuget/Newtonsoft.Json",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "2.4.0", "currentValue": "2.4.0",
"depName": "Serilog", "depName": "Serilog",
"depType": "nuget", "depType": "nuget",
"lineNumber": 20, "lineNumber": 20,
"purl": "pkg:nuget/Serilog",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "1.4.0", "currentValue": "1.4.0",
"depName": "Serilog.Extensions.Logging", "depName": "Serilog.Extensions.Logging",
"depType": "nuget", "depType": "nuget",
"lineNumber": 21, "lineNumber": 21,
"purl": "pkg:nuget/Serilog.Extensions.Logging",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "2.1.0", "currentValue": "2.1.0",
"depName": "Serilog.Sinks.Literate", "depName": "Serilog.Sinks.Literate",
"depType": "nuget", "depType": "nuget",
"lineNumber": 22, "lineNumber": 22,
"purl": "pkg:nuget/Serilog.Sinks.Literate",
"versionScheme": "semver",
}, },
Object { Object {
"currentVersion": "3.1.0", "currentValue": "3.1.0.5",
"depName": "Stateless", "depName": "Stateless",
"depType": "nuget", "depType": "nuget",
"lineNumber": 23, "lineNumber": 23,
"purl": "pkg:nuget/Stateless",
"skipReason": "not-version",
"versionScheme": "semver",
}, },
] ]
`; `;