Skip to content
Snippets Groups Projects
Unverified Commit 8f6bf44e authored by mhetzel's avatar mhetzel Committed by GitHub
Browse files

feat: add conan datasource and manager (#12009)

parent b7e267f6
Branches
Tags
No related merge requests found
Showing
with 1890 additions and 0 deletions
...@@ -4,6 +4,7 @@ import { AwsMachineImageDataSource } from './aws-machine-image'; ...@@ -4,6 +4,7 @@ import { AwsMachineImageDataSource } from './aws-machine-image';
import { BitBucketTagsDatasource } from './bitbucket-tags'; import { BitBucketTagsDatasource } from './bitbucket-tags';
import { CdnJsDatasource } from './cdnjs'; import { CdnJsDatasource } from './cdnjs';
import { ClojureDatasource } from './clojure'; import { ClojureDatasource } from './clojure';
import { ConanDatasource } from './conan';
import { CrateDatasource } from './crate'; import { CrateDatasource } from './crate';
import { DartDatasource } from './dart'; import { DartDatasource } from './dart';
import * as docker from './docker'; import * as docker from './docker';
...@@ -48,6 +49,7 @@ api.set(AwsMachineImageDataSource.id, new AwsMachineImageDataSource()); ...@@ -48,6 +49,7 @@ api.set(AwsMachineImageDataSource.id, new AwsMachineImageDataSource());
api.set('bitbucket-tags', new BitBucketTagsDatasource()); api.set('bitbucket-tags', new BitBucketTagsDatasource());
api.set('cdnjs', new CdnJsDatasource()); api.set('cdnjs', new CdnJsDatasource());
api.set('clojure', new ClojureDatasource()); api.set('clojure', new ClojureDatasource());
api.set(ConanDatasource.id, new ConanDatasource());
api.set('crate', new CrateDatasource()); api.set('crate', new CrateDatasource());
api.set('dart', new DartDatasource()); api.set('dart', new DartDatasource());
api.set('docker', docker); api.set('docker', docker);
......
{
"results" : [ ]
}
{
"results" : [ "bad/@conan/stable", "bad/1.3", "bad/1.8.1@conan", "bad/1.9.3@_/_", "bad1.20.3@_/_" ]
}
{
"results" : [ "poco/1.10.0@_/_", "poco/1.10.1@_/_", "poco/1.8.1@_/_", "poco/1.9.3@_/_", "poco/1.9.4@_/_" ]
}
import { regEx } from '../../util/regex';
export const defaultRegistryUrl = 'https://center.conan.io/';
export const datasource = 'conan';
export const conanDatasourceRegex = regEx(
/(?<name>[a-z\-_0-9]+)\/(?<version>[^@/\n]+)(?<userChannel>@\S+\/\S+)/,
'gim'
);
import { getPkgReleases } from '..';
import { Fixtures } from '../../../test/fixtures';
import * as httpMock from '../../../test/http-mock';
import * as conan from '../../versioning/conan';
import type { GetPkgReleasesConfig } from '../types';
import { defaultRegistryUrl } from './common';
import { ConanDatasource } from '.';
const pocoJson = Fixtures.get('poco.json');
const malformedJson = Fixtures.get('malformed.json');
const fakeJson = Fixtures.get('fake.json');
const datasource = ConanDatasource.id;
const config: GetPkgReleasesConfig = {
depName: '',
datasource,
versioning: conan.id,
registryUrls: [defaultRegistryUrl],
};
describe('datasource/conan/index', () => {
beforeEach(() => {
config.registryUrls = [defaultRegistryUrl];
});
describe('getReleases', () => {
it('handles bad return', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=fakepackage')
.reply(200, null);
config.depName = 'fakepackage';
expect(
await getPkgReleases({
...config,
lookupName: 'fakepackage/1.2@_/_',
})
).toBeNull();
});
it('handles empty return', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=fakepackage')
.reply(200, {});
config.depName = 'fakepackage';
expect(
await getPkgReleases({
...config,
lookupName: 'fakepackage/1.2@_/_',
})
).toBeNull();
});
it('handles bad registries', async () => {
httpMock
.scope('https://fake.bintray.com/')
.get('/v2/conans/search?q=poco')
.reply(404);
config.registryUrls = ['https://fake.bintray.com/'];
config.depName = 'poco';
expect(
await getPkgReleases({
...config,
lookupName: 'poco/1.2@_/_',
})
).toBeNull();
});
it('handles missing packages', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=fakepackage')
.reply(200, fakeJson);
config.depName = 'fakepackage';
expect(
await getPkgReleases({
...config,
lookupName: 'fakepackage/1.2@_/_',
})
).toBeNull();
});
it('processes real versioned data', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=poco')
.reply(200, pocoJson);
config.depName = 'poco';
expect(
await getPkgReleases({
...config,
lookupName: 'poco/1.2@_/_',
})
).toEqual({
registryUrl: 'https://center.conan.io',
releases: [
{
version: '1.8.1',
},
{
version: '1.9.3',
},
{
version: '1.9.4',
},
{
version: '1.10.0',
},
{
version: '1.10.1',
},
],
});
});
it('it handles mismatched userAndChannel versioned data', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=poco')
.reply(200, pocoJson);
config.depName = 'poco';
expect(
await getPkgReleases({
...config,
lookupName: 'poco/1.2@un/matched',
})
).toBeNull();
});
it('handles malformed packages', async () => {
httpMock
.scope(defaultRegistryUrl)
.get('/v2/conans/search?q=bad')
.reply(200, malformedJson);
config.depName = 'bad';
expect(
await getPkgReleases({
...config,
lookupName: 'bad/1.2@_/_',
})
).toEqual({
registryUrl: 'https://center.conan.io',
releases: [
{
version: '1.9.3',
},
],
});
});
it('handles non 404 errors', async () => {
httpMock
.scope('https://fake.bintray.com/')
.get('/v2/conans/search?q=poco')
.replyWithError('error');
config.registryUrls = ['https://fake.bintray.com/'];
config.depName = 'poco';
expect(
await getPkgReleases({
...config,
lookupName: 'poco/1.2@_/_',
})
).toBeNull();
});
it('handles missing slash on registries', async () => {
httpMock
.scope('https://fake.bintray.com/')
.get('/v2/conans/search?q=poco')
.reply(200, fakeJson);
config.registryUrls = ['https://fake.bintray.com'];
config.depName = 'poco';
expect(
await getPkgReleases({
...config,
lookupName: 'poco/1.2@_/_',
})
).toBeNull();
});
});
});
import { logger } from '../../logger';
import { cache } from '../../util/cache/package/decorator';
import { ensureTrailingSlash, joinUrlParts } from '../../util/url';
import { Datasource } from '../datasource';
import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
import { conanDatasourceRegex, datasource, defaultRegistryUrl } from './common';
import type { ConanJSON } from './types';
export class ConanDatasource extends Datasource {
static readonly id = datasource;
override readonly defaultRegistryUrls = [defaultRegistryUrl];
override readonly caching = true;
override readonly registryStrategy = 'merge';
constructor() {
super(ConanDatasource.id);
}
@cache({
namespace: `datasource-${datasource}`,
key: ({ registryUrl, lookupName }: GetReleasesConfig) =>
`${registryUrl}:${lookupName}`,
})
async getReleases({
registryUrl,
lookupName,
}: GetReleasesConfig): Promise<ReleaseResult | null> {
const depName = lookupName.split('/')[0];
const userAndChannel = '@' + lookupName.split('@')[1];
logger.trace({ depName, registryUrl }, 'Looking up conan api dependency');
if (registryUrl) {
const url = ensureTrailingSlash(registryUrl);
const lookupUrl = joinUrlParts(url, `v2/conans/search?q=${depName}`);
try {
const rep = await this.http.getJson<ConanJSON>(lookupUrl);
const versions = rep?.body;
if (versions) {
logger.trace({ lookupUrl }, 'Got conan api result');
const dep: ReleaseResult = { releases: [] };
for (const resultString of Object.values(versions.results || {})) {
const fromMatch = conanDatasourceRegex.exec(resultString);
if (fromMatch?.groups?.version && fromMatch?.groups?.userChannel) {
const version = fromMatch.groups.version;
if (fromMatch.groups.userChannel === userAndChannel) {
const result: Release = {
version,
};
dep.releases.push(result);
}
}
}
return dep;
}
} catch (err) {
this.handleGenericErrors(err);
}
}
return null;
}
}
export interface ConanJSON {
results?: Record<string, string>;
}
...@@ -15,6 +15,7 @@ import * as circleci from './circleci'; ...@@ -15,6 +15,7 @@ import * as circleci from './circleci';
import * as cloudbuild from './cloudbuild'; import * as cloudbuild from './cloudbuild';
import * as cocoapods from './cocoapods'; import * as cocoapods from './cocoapods';
import * as composer from './composer'; import * as composer from './composer';
import * as conan from './conan';
import * as depsEdn from './deps-edn'; import * as depsEdn from './deps-edn';
import * as dockerCompose from './docker-compose'; import * as dockerCompose from './docker-compose';
import * as dockerfile from './dockerfile'; import * as dockerfile from './dockerfile';
...@@ -86,6 +87,7 @@ api.set('circleci', circleci); ...@@ -86,6 +87,7 @@ api.set('circleci', circleci);
api.set('cloudbuild', cloudbuild); api.set('cloudbuild', cloudbuild);
api.set('cocoapods', cocoapods); api.set('cocoapods', cocoapods);
api.set('composer', composer); api.set('composer', composer);
api.set('conan', conan);
api.set('deps-edn', depsEdn); api.set('deps-edn', depsEdn);
api.set('docker-compose', dockerCompose); api.set('docker-compose', dockerCompose);
api.set('dockerfile', dockerfile); api.set('dockerfile', dockerfile);
......
from conans import ConanFile
class Pkg(ConanFile):
python_requires = "pyreq/0.1@user/channel" # recipe to reuse code from
build_requires = "tool_a/0.2@user/testing", "tool_b/0.2@user/testing"
requires = "req_a/1.0", "req_l/2.1@otheruser/testing"
requires = [("req_b/0.1@user/testing"),
("req_d/0.2@dummy/stable", "override"),
("req_e/2.1@coder/beta", "private")]
requires = (("req_c/1.0@user/stable", "private"), )
requires = ("req_f/1.0@user/stable", ("req_h/3.0@other/beta", "override"))
requires = "req_g/[>1.0 <1.8]@user/stable"
def requirements(self):
if self.options.myoption:
self.requires("req_i/1.2@drl/testing")
else:
self.requires("req_i/2.2@drl/stable")
self.requires("req_k/1.2@drl/testing", private=True, override=False)
def build_requirements(self):
if self.settings.os == "Windows":
self.build_requires("tool_win/0.1@user/stable")
[requires]
poco/1.9.4
zlib/[~1.2.3, loose=False]
fake/8.62.134@test/dev
[build_requires]
7zip/[>1.1 <2.1, include_prerelease=True]
curl/[~1.2.3, loose=False, include_prerelease=True]@test/dev
boost/[>1.1 <2.1]
catch2/[2.8]
openssl/[~=3.0]@test/prod
cmake/[>1.1 || 0.8]
cryptopp/[1.2.7 || >=1.2.9 <2.0.0]@test/local
#commentedout/1.2
# commentedout/3.4
[generators]
xcode
cmake
qmake
[options]
poco:shared=True
openssl:shared=True
# A comment
[imports]
bin, *.dll -> ./bin # Copies all dll files from packages bin folder to my local "bin" folder
lib, *.dylib* -> ./bin # Copies all dylib files from packages lib folder to my local "bin" folder
\ No newline at end of file
[generators]
xcode
cmake
qmake
[options]
poco:shared=True
openssl:shared=True
# A comment
import { Fixtures } from '../../../test/fixtures';
import { extractPackageFile } from '.';
const conanfile1 = Fixtures.get('conanfile.txt');
const conanfile2 = Fixtures.get('conanfile2.txt');
const conanfile3 = Fixtures.get('conanfile.py');
describe('manager/conan/extract', () => {
describe('extractPackageFile', () => {
it('returns null for empty', () => {
expect(extractPackageFile('nothing here')).toBeNull();
});
it('extracts multiple image lines from conanfile.txt', () => {
const res = extractPackageFile(conanfile1);
expect(res?.deps).toEqual([
{
currentValue: '1.9.4',
depName: 'poco',
depType: 'requires',
lookupName: 'poco/1.9.4@_/_',
replaceString: 'poco/1.9.4',
},
{
currentValue: '[~1.2.3, loose=False]',
depName: 'zlib',
depType: 'requires',
lookupName: 'zlib/[~1.2.3, loose=False]@_/_',
replaceString: 'zlib/[~1.2.3, loose=False]',
},
{
currentValue: '8.62.134',
depName: 'fake',
depType: 'requires',
lookupName: 'fake/8.62.134@test/dev',
replaceString: 'fake/8.62.134@test/dev',
},
{
currentValue: '[>1.1 <2.1, include_prerelease=True]',
depName: '7zip',
depType: 'build_requires',
lookupName: '7zip/[>1.1 <2.1, include_prerelease=True]@_/_',
replaceString: '7zip/[>1.1 <2.1, include_prerelease=True]',
},
{
currentValue: '[~1.2.3, loose=False, include_prerelease=True]',
depName: 'curl',
depType: 'build_requires',
lookupName:
'curl/[~1.2.3, loose=False, include_prerelease=True]@test/dev',
replaceString:
'curl/[~1.2.3, loose=False, include_prerelease=True]@test/dev',
},
{
currentValue: '[>1.1 <2.1]',
depName: 'boost',
depType: 'build_requires',
lookupName: 'boost/[>1.1 <2.1]@_/_',
replaceString: 'boost/[>1.1 <2.1]',
},
{
currentValue: '[2.8]',
depName: 'catch2',
depType: 'build_requires',
lookupName: 'catch2/[2.8]@_/_',
replaceString: 'catch2/[2.8]',
},
{
currentValue: '[~=3.0]',
depName: 'openssl',
depType: 'build_requires',
lookupName: 'openssl/[~=3.0]@test/prod',
replaceString: 'openssl/[~=3.0]@test/prod',
},
{
currentValue: '[>1.1 || 0.8]',
depName: 'cmake',
depType: 'build_requires',
lookupName: 'cmake/[>1.1 || 0.8]@_/_',
replaceString: 'cmake/[>1.1 || 0.8]',
},
{
currentValue: '[1.2.7 || >=1.2.9 <2.0.0]',
depName: 'cryptopp',
depType: 'build_requires',
lookupName: 'cryptopp/[1.2.7 || >=1.2.9 <2.0.0]@test/local',
replaceString: 'cryptopp/[1.2.7 || >=1.2.9 <2.0.0]@test/local',
},
]);
});
it('extracts multiple 0 lines from conanfile.txt', () => {
const res = extractPackageFile(conanfile2);
expect(res).toBeNull();
});
it('extracts multiple image lines from conanfile.py', () => {
const res = extractPackageFile(conanfile3);
expect(res?.deps).toEqual([
{
currentValue: '0.1',
depName: 'pyreq',
depType: 'python_requires',
lookupName: 'pyreq/0.1@user/channel',
replaceString: 'pyreq/0.1@user/channel',
},
{
currentValue: '0.2',
depName: 'tool_a',
depType: 'build_requires',
lookupName: 'tool_a/0.2@user/testing',
replaceString: 'tool_a/0.2@user/testing',
},
{
currentValue: '0.2',
depName: 'tool_b',
depType: 'build_requires',
lookupName: 'tool_b/0.2@user/testing',
replaceString: 'tool_b/0.2@user/testing',
},
{
currentValue: '1.0',
depName: 'req_a',
depType: 'requires',
lookupName: 'req_a/1.0@_/_',
replaceString: 'req_a/1.0',
},
{
currentValue: '2.1',
depName: 'req_l',
depType: 'requires',
lookupName: 'req_l/2.1@otheruser/testing',
replaceString: 'req_l/2.1@otheruser/testing',
},
{
currentValue: '0.1',
depName: 'req_b',
depType: 'requires',
lookupName: 'req_b/0.1@user/testing',
replaceString: 'req_b/0.1@user/testing',
},
{
currentValue: '0.2',
depName: 'req_d',
depType: 'requires',
lookupName: 'req_d/0.2@dummy/stable',
replaceString: 'req_d/0.2@dummy/stable',
},
{
currentValue: '2.1',
depName: 'req_e',
depType: 'requires',
lookupName: 'req_e/2.1@coder/beta',
replaceString: 'req_e/2.1@coder/beta',
},
{
currentValue: '1.0',
depName: 'req_c',
depType: 'requires',
lookupName: 'req_c/1.0@user/stable',
replaceString: 'req_c/1.0@user/stable',
},
{
currentValue: '1.0',
depName: 'req_f',
depType: 'requires',
lookupName: 'req_f/1.0@user/stable',
replaceString: 'req_f/1.0@user/stable',
},
{
currentValue: '3.0',
depName: 'req_h',
depType: 'requires',
lookupName: 'req_h/3.0@other/beta',
replaceString: 'req_h/3.0@other/beta',
},
{
currentValue: '[>1.0 <1.8]',
depName: 'req_g',
depType: 'requires',
lookupName: 'req_g/[>1.0 <1.8]@user/stable',
replaceString: 'req_g/[>1.0 <1.8]@user/stable',
},
{
currentValue: '1.2',
depName: 'req_i',
depType: 'requires',
lookupName: 'req_i/1.2@drl/testing',
replaceString: 'req_i/1.2@drl/testing',
},
{
currentValue: '2.2',
depName: 'req_i',
depType: 'requires',
lookupName: 'req_i/2.2@drl/stable',
replaceString: 'req_i/2.2@drl/stable',
},
{
currentValue: '1.2',
depName: 'req_k',
depType: 'requires',
lookupName: 'req_k/1.2@drl/testing',
replaceString: 'req_k/1.2@drl/testing',
},
{
currentValue: '0.1',
depName: 'tool_win',
depType: 'build_requires',
lookupName: 'tool_win/0.1@user/stable',
replaceString: 'tool_win/0.1@user/stable',
},
]);
});
});
});
import is from '@sindresorhus/is';
import { regEx } from '../../util/regex';
import type { PackageDependency, PackageFile } from '../types';
const regex = regEx(
`(?<name>[-_a-z0-9]+)/(?<version>[^@\n{*"']+)(?<userChannel>@[-_a-zA-Z0-9]+/[^\n.{*"' ]+)?`
);
function setDepType(content: string, originalType: string): string {
let depType = originalType;
if (content.includes('python_requires')) {
depType = 'python_requires';
} else if (content.includes('build_require')) {
depType = 'build_requires';
} else if (content.includes('requires')) {
depType = 'requires';
}
return depType;
}
export function extractPackageFile(content: string): PackageFile | null {
// only process sections where requirements are defined
const sections = content.split(/def |\n\[/).filter(
(part) =>
part.includes('python_requires') || // only matches python_requires
part.includes('build_require') || // matches [build_requires], build_requirements(), and build_requires
part.includes('require') // matches [requires], requirements(), and requires
);
const deps: PackageDependency[] = [];
for (const section of sections) {
let depType = setDepType(section, 'requires');
const rawLines = section.split('\n').filter(is.nonEmptyString);
for (const rawline of rawLines) {
// don't process after a comment
const sanitizedLine = rawline.split('#')[0].split('//')[0];
if (sanitizedLine) {
depType = setDepType(sanitizedLine, depType);
// extract all dependencies from each line
const lines = sanitizedLine.split(/["'],/);
for (const line of lines) {
const matches = regex.exec(line.trim());
if (matches?.groups) {
let dep: PackageDependency = {};
const depName = matches.groups?.name;
const currentValue = matches.groups?.version.trim();
let replaceString = `${depName}/${currentValue}`;
// conan uses @_/_ as a placeholder for no userChannel
let userAndChannel = '@_/_';
if (matches.groups.userChannel) {
userAndChannel = matches.groups.userChannel;
replaceString = `${depName}/${currentValue}${userAndChannel}`;
}
const lookupName = `${depName}/${currentValue}${userAndChannel}`;
dep = {
...dep,
depName,
lookupName,
currentValue,
replaceString,
depType,
};
deps.push(dep);
}
}
}
}
}
return deps.length ? { deps } : null;
}
export { extractPackageFile } from './extract';
import { ConanDatasource } from '../../datasource/conan';
import * as conan from '../../versioning/conan';
export const defaultConfig = {
fileMatch: ['(^|/)conanfile\\.(txt|py)$'],
datasource: ConanDatasource.id,
versioning: conan.id,
rangeStrategy: 'bump',
};
export const supportedDatasources = [ConanDatasource.id];
Automated dependency updates for Conan
Renovate can upgrade dependencies in `conanfile.txt` or `conanfile.py` files.
How it works:
1. Renovate searches in each repository for any `conanfile.txt` or `conanfile.py` file
1. Renovate extracts existing dependencies from:
- the `[requires]` and `[build_requires]` sections in the `conanfile.txt` format
- the `requirements()` and `build_requirements()` functions in the `conanfile.py` format
- and the `python_requires`, `requires` and `build_requires` variables in the `conanfile.py` format
1. Renovate resolves the dependency's version using the Conan v2 API
1. If Renovate finds an update, Renovate will update `conanfile.txt` or `conanfile.py`
Enabling Conan updating
Renovate updates Conan packages by default.
import * as amazonMachineImage from './aws-machine-image'; import * as amazonMachineImage from './aws-machine-image';
import * as cargo from './cargo'; import * as cargo from './cargo';
import * as composer from './composer'; import * as composer from './composer';
import * as conan from './conan';
import * as docker from './docker'; import * as docker from './docker';
import * as git from './git'; import * as git from './git';
import * as gradle from './gradle'; import * as gradle from './gradle';
...@@ -30,6 +31,7 @@ export default api; ...@@ -30,6 +31,7 @@ export default api;
api.set(amazonMachineImage.id, amazonMachineImage.api); api.set(amazonMachineImage.id, amazonMachineImage.api);
api.set('cargo', cargo.api); api.set('cargo', cargo.api);
api.set('composer', composer.api); api.set('composer', composer.api);
api.set('conan', conan.api);
api.set('docker', docker.api); api.set('docker', docker.api);
api.set('git', git.api); api.set('git', git.api);
api.set('gradle', gradle.api); api.set('gradle', gradle.api);
......
import * as semver from 'semver';
import { regEx } from '../../util/regex';
export function makeVersion(
version: string,
options: semver.Options
): string | boolean | null {
const splitVersion = version.split('.');
const prerelease = semver.prerelease(version, options);
if (prerelease && !options.includePrerelease) {
if (!Number.isNaN(parseInt(prerelease.toString()[0], 10))) {
const stringVersion = `${splitVersion[0]}.${splitVersion[1]}.${splitVersion[2]}`;
return semver.valid(stringVersion, options);
}
return false;
}
if (
options.loose &&
!semver.valid(version, options) &&
splitVersion.length !== 3
) {
return semver.valid(semver.coerce(version, options), options);
}
return semver.valid(version, options);
}
export function cleanVersion(version: string): string {
if (version) {
return version
.replace(regEx(/,|\[|\]|"|include_prerelease=|loose=|True|False/g), '')
.trim();
}
return version;
}
export function getOptions(input: string): {
loose: boolean;
includePrerelease: boolean;
} {
let includePrerelease = false;
let loose = true;
if (input) {
includePrerelease =
input.includes('include_prerelease=True') &&
!input.includes('include_prerelease=False');
loose = input.includes('loose=True') || !input.includes('loose=False');
}
return { loose, includePrerelease };
}
export function containsOperators(input: string): boolean {
return regEx('[<=>^~]').test(input);
}
export function matchesWithOptions(
version: string,
cleanRange: string,
options: semver.Options
): boolean {
let cleanedVersion = version;
if (
cleanedVersion &&
semver.prerelease(cleanedVersion) &&
options.includePrerelease
) {
const coercedVersion = semver.coerce(cleanedVersion)?.raw;
cleanedVersion = coercedVersion ? coercedVersion : '';
}
return semver.satisfies(cleanedVersion, cleanRange, options);
}
export function findSatisfyingVersion(
versions: string[],
range: string,
compareRt: number
): string | null {
const options = getOptions(range);
let cur: any = null;
let curSV: any = null;
let index = 0;
let curIndex = -1;
for (const v of versions) {
const versionFromList = makeVersion(v, options);
if (typeof versionFromList === 'string') {
const cleanedVersion = cleanVersion(versionFromList);
const options = getOptions(range);
const cleanRange = cleanVersion(range);
if (matchesWithOptions(cleanedVersion, cleanRange, options)) {
if (
!cur ||
semver.compare(curSV, versionFromList, options) === compareRt
) {
cur = versionFromList;
curIndex = index;
curSV = new semver.SemVer(cur, options);
}
}
}
index += 1;
}
if (curIndex >= 0) {
return versions[curIndex];
}
return null;
}
This diff is collapsed.
import * as semver from 'semver';
import { api as looseAPI } from '../loose';
import type { NewValueConfig, VersioningApi } from '../types';
import {
cleanVersion,
findSatisfyingVersion,
getOptions,
makeVersion,
matchesWithOptions,
} from './common';
import {
bumpRange,
getMajor,
getMinor,
getPatch,
replaceRange,
widenRange,
} from './range';
export const id = 'conan';
export const displayName = 'conan';
export const urls = [
'https://semver.org/',
'https://github.com/podhmo/python-node-semver',
'https://github.com/podhmo/python-node-semver/tree/master/examples',
'https://docs.conan.io/en/latest/versioning/version_ranges.html#version-ranges',
];
export const supportsRanges = true;
export const supportedRangeStrategies = ['auto', 'bump', 'widen', 'replace'];
const MIN = 1;
const MAX = -1;
function isVersion(input: string): boolean {
if (input && !input.includes('[')) {
const qualifiers = getOptions(input);
const version = cleanVersion(input);
if (qualifiers.loose) {
if (looseAPI.isVersion(version)) {
return true;
}
}
return makeVersion(version, qualifiers) !== null;
}
return false;
}
function isValid(input: string): boolean {
const version = cleanVersion(input);
const qualifiers = getOptions(input);
if (makeVersion(version, qualifiers)) {
return version !== null;
}
return semver.validRange(version, qualifiers) !== null;
}
function equals(version: string, other: string): boolean {
const cleanedVersion = cleanVersion(version);
const cleanOther = cleanVersion(other);
const options = { loose: true, includePrerelease: true };
const looseResult = looseAPI.equals(cleanedVersion, cleanOther);
try {
return semver.eq(cleanedVersion, cleanOther, options) || looseResult;
} catch {
return looseResult;
}
}
function isGreaterThan(version: string, other: string): boolean {
const cleanedVersion = cleanVersion(version);
const cleanOther = cleanVersion(other);
const options = { loose: true, includePrerelease: true };
const looseResult = looseAPI.isGreaterThan(cleanedVersion, cleanOther);
try {
return semver.gt(cleanedVersion, cleanOther, options) || looseResult;
} catch {
return looseResult;
}
}
function isLessThanRange(version: string, range: string): boolean {
const cleanedVersion = cleanVersion(version);
const cleanRange = cleanVersion(range);
const options = getOptions(range);
const looseResult: any = looseAPI.isLessThanRange?.(
cleanedVersion,
cleanRange
);
try {
return semver.ltr(cleanedVersion, cleanRange, options) || looseResult;
} catch {
return looseResult;
}
}
function sortVersions(version: string, other: string): number {
const cleanedVersion = cleanVersion(version);
const cleanOther = cleanVersion(other);
const options = { loose: true, includePrerelease: true };
try {
return semver.compare(cleanedVersion, cleanOther, options);
} catch {
return looseAPI.sortVersions(cleanedVersion, cleanOther);
}
}
function matches(version: string, range: string): boolean {
if (isVersion(version) && isVersion(range)) {
return true;
}
const cleanedVersion = cleanVersion(version);
const options = getOptions(range);
const cleanRange = cleanVersion(range);
return matchesWithOptions(cleanedVersion, cleanRange, options);
}
function isCompatible(version: string, range: string): boolean {
if (isVersion(version) && isVersion(range)) {
return true;
}
const options = getOptions(range);
const compatibleVersion = makeVersion(version, options);
if (compatibleVersion) {
return !isLessThanRange(version, range);
}
return false;
}
function isStable(version: string): boolean {
const cleanedVersion = cleanVersion(version);
const options = getOptions(version);
if (
!options.includePrerelease &&
semver.prerelease(cleanedVersion, options)
) {
return false;
}
return true;
}
function minSatisfyingVersion(
versions: string[],
range: string
): string | null {
return findSatisfyingVersion(versions, range, MIN);
}
function getSatisfyingVersion(
versions: string[],
range: string
): string | null {
return findSatisfyingVersion(versions, range, MAX);
}
function getNewValue({
currentValue,
rangeStrategy,
currentVersion,
newVersion,
}: NewValueConfig): string | null {
const cleanRange = cleanVersion(currentValue);
if (isVersion(currentValue) || rangeStrategy === 'pin') {
return newVersion;
}
const options = getOptions(currentValue);
let newValue: any = '';
if (rangeStrategy === 'widen') {
newValue = widenRange(
{ currentValue: cleanRange, rangeStrategy, currentVersion, newVersion },
options
);
} else if (rangeStrategy === 'bump') {
newValue = bumpRange(
{ currentValue: cleanRange, rangeStrategy, currentVersion, newVersion },
options
);
} else {
newValue = replaceRange({
currentValue: cleanRange,
rangeStrategy,
currentVersion,
newVersion,
});
}
if (newValue) {
return currentValue.replace(cleanRange, newValue);
}
return null;
}
export const api: VersioningApi = {
equals,
getMajor,
getMinor,
getNewValue,
getPatch,
isCompatible,
isGreaterThan,
isLessThanRange,
isSingleVersion: isVersion,
isStable,
isValid,
isVersion,
matches,
getSatisfyingVersion,
minSatisfyingVersion,
sortVersions,
};
export default api;
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment