diff --git a/lib/constants/data-binary-source.ts b/lib/constants/data-binary-source.ts index e1e610c13bf4e2bbc92cc85af0119f8547d4d57f..9f6d528d5763fdcaf7fe48fc7fdb71a50542f40f 100644 --- a/lib/constants/data-binary-source.ts +++ b/lib/constants/data-binary-source.ts @@ -1,5 +1,6 @@ // Datasource export const DATASOURCE_CARGO = 'cargo'; +// export const DATASOURCE_CDNJS = 'cdnjs'; export const DATASOURCE_DART = 'dart'; export const DATASOURCE_DOCKER = 'docker'; export const DATASOURCE_GIT_SUBMODULES = 'gitSubmodules'; diff --git a/lib/datasource/cdnjs/index.ts b/lib/datasource/cdnjs/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..acac254aa00f899ff9b7605fa19ad0b0f400dce8 --- /dev/null +++ b/lib/datasource/cdnjs/index.ts @@ -0,0 +1,76 @@ +import { logger } from '../../logger'; +import got from '../../util/got'; +import { ReleaseResult, PkgReleaseConfig } from '../common'; +import { DATASOURCE_FAILURE } from '../../constants/error-messages'; + +interface CdnjsAsset { + version: string; + files: string[]; +} + +interface CdnjsResponse { + homepage?: string; + repository?: { + type: 'git' | unknown; + url?: string; + }; + assets?: CdnjsAsset[]; +} + +export async function getPkgReleases({ + lookupName, +}: Partial<PkgReleaseConfig>): Promise<ReleaseResult | null> { + // istanbul ignore if + if (!lookupName) { + logger.warn('CDNJS lookup failure: empty lookupName'); + return null; + } + + const [depName, ...assetParts] = lookupName.split('/'); + const assetName = assetParts.join('/'); + const url = `https://api.cdnjs.com/libraries/${depName}`; + + try { + const res = await got(url, { json: true }); + + const cdnjsResp: CdnjsResponse = res.body; + + if (!cdnjsResp || !cdnjsResp.assets) { + logger.warn({ depName }, `Invalid CDNJS response`); + return null; + } + + const { assets, homepage, repository } = cdnjsResp; + + const releases = assets + .filter(({ files }) => files.indexOf(assetName) !== -1) + .map(({ version }) => ({ version })); + + const result: ReleaseResult = { releases }; + + if (homepage) result.homepage = homepage; + if (repository && repository.url) result.sourceUrl = repository.url; + + return result; + } catch (err) { + const errorData = { depName, err }; + + if ( + err.statusCode === 429 || + (err.statusCode >= 500 && err.statusCode < 600) + ) { + logger.warn({ lookupName, err }, `Cdnjs registry failure`); + throw new Error(DATASOURCE_FAILURE); + } + + if (err.statusCode === 401) { + logger.debug(errorData, 'Authorization error'); + } else if (err.statusCode === 404) { + logger.debug(errorData, 'Package lookup error'); + } else { + logger.warn(errorData, 'Cdnjs lookup failure: Unknown error'); + } + } + + return null; +} diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts index 435e3ec44453d02a32dad307c67550440ac16915..9e7d1d7deda0050dd770d46ddd230de0a7e75184 100644 --- a/lib/datasource/index.ts +++ b/lib/datasource/index.ts @@ -3,6 +3,7 @@ import { addMetaData } from './metadata'; import * as versioning from '../versioning'; import * as cargo from './cargo'; +import * as cdnjs from './cdnjs'; import * as dart from './dart'; import * as docker from './docker'; import * as hex from './hex'; @@ -37,6 +38,7 @@ export * from './common'; const datasources: Record<string, Datasource> = { cargo, + cdnjs, dart, docker, helm, diff --git a/test/datasource/__snapshots__/cdnjs.spec.ts.snap b/test/datasource/__snapshots__/cdnjs.spec.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..6f4c23d99650b62aff8e4919eeb8ecec264db42e --- /dev/null +++ b/test/datasource/__snapshots__/cdnjs.spec.ts.snap @@ -0,0 +1,100 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`datasource/cdnjs getPkgReleases filters releases by asset presence 1`] = ` +Object { + "homepage": "http://bulma.io", + "releases": Array [ + Object { + "version": "0.7.5", + }, + ], + "sourceUrl": "git+https://github.com/jgthms/bulma.git", +} +`; + +exports[`datasource/cdnjs getPkgReleases processes real data 1`] = ` +Object { + "homepage": "https://d3js.org/d3-force/", + "releases": Array [ + Object { + "version": "1.1.0", + }, + Object { + "version": "1.0.6", + }, + Object { + "version": "1.0.5", + }, + Object { + "version": "1.0.4", + }, + Object { + "version": "1.0.3", + }, + Object { + "version": "1.0.2", + }, + Object { + "version": "1.0.1", + }, + Object { + "version": "1.0.0", + }, + Object { + "version": "0.7.1", + }, + Object { + "version": "0.7.0", + }, + Object { + "version": "0.6.3", + }, + Object { + "version": "0.6.2", + }, + Object { + "version": "0.6.1", + }, + Object { + "version": "0.6.0", + }, + Object { + "version": "0.5.0", + }, + Object { + "version": "0.4.1", + }, + Object { + "version": "0.4.0", + }, + Object { + "version": "0.3.0", + }, + Object { + "version": "0.2.2", + }, + Object { + "version": "0.2.1", + }, + Object { + "version": "0.2.0", + }, + Object { + "version": "0.1.0", + }, + Object { + "version": "0.0.4", + }, + Object { + "version": "0.0.3", + }, + Object { + "version": "0.0.2", + }, + Object { + "version": "0.0.1", + }, + ], + "sourceUrl": "https://github.com/d3/d3-force.git", +} +`; diff --git a/test/datasource/cdnjs.spec.ts b/test/datasource/cdnjs.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..30991b70c768e25ca92600a8586d3994bc3d8c20 --- /dev/null +++ b/test/datasource/cdnjs.spec.ts @@ -0,0 +1,101 @@ +import fs from 'fs'; +import _got from '../../lib/util/got'; +import { getPkgReleases } from '../../lib/datasource/cdnjs'; +import { DATASOURCE_FAILURE } from '../../lib/constants/error-messages'; + +const got: any = _got; + +let res1 = fs.readFileSync( + 'test/datasource/cdnjs/_fixtures/d3-force.json', + 'utf8' +); +res1 = JSON.parse(res1); + +let res2 = fs.readFileSync( + 'test/datasource/cdnjs/_fixtures/bulma.json', + 'utf8' +); +res2 = JSON.parse(res2); + +jest.mock('../../lib/util/got'); + +describe('datasource/cdnjs', () => { + describe('getPkgReleases', () => { + beforeEach(() => { + jest.clearAllMocks(); + global.repoCache = {}; + return global.renovateCache.rmAll(); + }); + it('returns null for empty result', async () => { + got.mockReturnValueOnce(null); + expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull(); + }); + it('returns null for missing fields', async () => { + got.mockReturnValueOnce({}); + expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull(); + }); + it('returns null for 404', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 404, + }) + ); + expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull(); + }); + it('returns null for 401', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 401, + }) + ); + expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull(); + }); + it('throws for 429', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 429, + }) + ); + await expect( + getPkgReleases({ lookupName: 'foo/bar' }) + ).rejects.toThrowError(DATASOURCE_FAILURE); + }); + it('throws for 5xx', async () => { + got.mockImplementationOnce(() => + Promise.reject({ + statusCode: 502, + }) + ); + await expect( + getPkgReleases({ lookupName: 'foo/bar' }) + ).rejects.toThrowError(DATASOURCE_FAILURE); + }); + it('returns null for unknown error', async () => { + got.mockImplementationOnce(() => { + throw new Error(); + }); + expect(await getPkgReleases({ lookupName: 'foo/bar' })).toBeNull(); + }); + it('returns null with wrong auth token', async () => { + got.mockReturnValueOnce( + Promise.reject({ + statusCode: 401, + }) + ); + const res = await getPkgReleases({ lookupName: 'foo/bar' }); + expect(res).toBeNull(); + }); + it('processes real data', async () => { + got.mockReturnValueOnce({ body: res1 }); + const res = await getPkgReleases({ lookupName: 'd3-force/d3-force.js' }); + expect(res).toMatchSnapshot(); + }); + it('filters releases by asset presence', async () => { + got.mockReturnValueOnce({ body: res2 }); + const res = await getPkgReleases({ + lookupName: 'bulma/only/0.7.5/style.css', + }); + expect(res).toMatchSnapshot(); + }); + }); +}); diff --git a/test/datasource/cdnjs/_fixtures/bulma.json b/test/datasource/cdnjs/_fixtures/bulma.json new file mode 100644 index 0000000000000000000000000000000000000000..b972a5fe3c8cfd901b55e47ef05573773ec98d39 --- /dev/null +++ b/test/datasource/cdnjs/_fixtures/bulma.json @@ -0,0 +1,487 @@ +{ + "name": "bulma", + "filename": "css/bulma.min.css", + "version": "0.8.0", + "description": "Modern CSS framework based on Flexbox", + "repository": { + "type": "git", + "url": "git+https://github.com/jgthms/bulma.git" + }, + "keywords": [ + "css", + "sass", + "flexbox", + "responsive", + "framework" + ], + "author": "bulma", + "homepage": "http://bulma.io", + "autoupdate": { + "source": "npm", + "target": "bulma" + }, + "license": "MIT", + "assets": [ + { + "version": "0.8.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css" + ] + }, + { + "version": "0.7.5", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "only/0.7.5/style.css" + ] + }, + { + "version": "0.7.4", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css" + ] + }, + { + "version": "0.7.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css" + ] + }, + { + "version": "0.7.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css" + ] + }, + { + "version": "0.7.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css" + ] + }, + { + "version": "0.6.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.6.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.6.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.5.3", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.5.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.5.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.5.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.4.4", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.4.3", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.4.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.4.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.4.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.3.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.3.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.3.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.2.3", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.2.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.2.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.1.2", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.1.1", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.1.0", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.28", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.27", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.26", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.25", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.24", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.23", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.22", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.21", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.20", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.19", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.18", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.17", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.16", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.15", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.14", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.13", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.12", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.11", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.10", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.9", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.8", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.7", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.6", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.5", + "files": [ + "css/bulma.css", + "css/bulma.css.map", + "css/bulma.min.css", + "css/bulma.min.css.map" + ] + }, + { + "version": "0.0.4", + "files": [ + "css/bulma.css", + "css/bulma.min.css" + ] + } + ] +} diff --git a/test/datasource/cdnjs/_fixtures/d3-force.json b/test/datasource/cdnjs/_fixtures/d3-force.json new file mode 100644 index 0000000000000000000000000000000000000000..8d4ffd6ed6fb4a5cffe6242845c241842f974a39 --- /dev/null +++ b/test/datasource/cdnjs/_fixtures/d3-force.json @@ -0,0 +1,211 @@ +{ + "name": "d3-force", + "filename": "d3-force.min.js", + "version": "1.1.0", + "description": "Force-directed graph layout using velocity Verlet integration.", + "repository": { + "type": "git", + "url": "https://github.com/d3/d3-force.git" + }, + "keywords": [ + "d3", + "d3-module", + "layout", + "network", + "graph", + "force", + "verlet", + "infovis" + ], + "author": "d3-force", + "homepage": "https://d3js.org/d3-force/", + "autoupdate": { + "source": "npm", + "target": "d3-force" + }, + "license": "BSD-3-Clause", + "assets": [ + { + "version": "1.1.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.6", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.5", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.4", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.3", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.2", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "1.0.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.7.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.7.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.6.3", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.6.2", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.6.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.6.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.5.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.4.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.4.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.3.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.2.2", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.2.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.2.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.1.0", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.0.4", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.0.3", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.0.2", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + }, + { + "version": "0.0.1", + "files": [ + "d3-force.js", + "d3-force.min.js" + ] + } + ] +}