diff --git a/.eslintignore b/.eslintignore index 916c8d42900362c81622dc0b88a4c968fbb61bc3..dd358e38fe3be7b963ed943ce80e6e607ce98105 100644 --- a/.eslintignore +++ b/.eslintignore @@ -10,3 +10,6 @@ coverage **/__mocks__/** **/*.d.ts /config.js + +# generated typescript code +**/*.generated.ts diff --git a/.eslintrc.js b/.eslintrc.js index b8cecc2854547a88898058e57dc887e2cc107d52..6499509be7eededff17442a1462f0ae4ebafd938 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,7 @@ module.exports = { parserOptions: { ecmaVersion: 9, tsconfigRootDir: __dirname, - project: ['./tsconfig.json'], + project: ['./tsconfig.lint.json'], extraFileExtensions: ['.mjs'], }, rules: { diff --git a/.gitignore b/.gitignore index 77f3cfb0fb37a4e4feccd3e3852eaf06f8164d4f..1a12fbfd8637ac64d54d1d3fb915113741f3b542 100644 --- a/.gitignore +++ b/.gitignore @@ -15,4 +15,6 @@ renovate-0.0.0-semantic-release.tgz junit.xml /test-results renovate-schema.json + +**/*.generated.ts test/datasource/nuget/_fixtures/obj diff --git a/.prettierignore b/.prettierignore index f74b40cc589e9878c341f1fff2453c177c605959..1edda6b04ce61838aa69248071ba11c7c6ea6359 100644 --- a/.prettierignore +++ b/.prettierignore @@ -12,3 +12,6 @@ package.json **/__fixtures__/ **/__mocks__/ bin/yarn* + +# generated typescript code +**/*.generated.ts diff --git a/.babelrc b/babel.config.json similarity index 88% rename from .babelrc rename to babel.config.json index 1caf31a8b372456d2af2a06a048e2e5ccb5e71f1..71ffbe280854e38f180888b3b6a6ed888d93e11f 100644 --- a/.babelrc +++ b/babel.config.json @@ -17,7 +17,6 @@ "sourceMaps": true, "retainLines": true, - // https://github.com/facebook/jest/issues/5920 "env": { "test": { "plugins": ["dynamic-import-node"] diff --git a/lib/datasource/common.ts b/lib/datasource/common.ts index a7eea21274b972fa32d7cc3c300325f2d36ff143..d4c12a676fe10f592897eab75c3c0e1e6445366f 100644 --- a/lib/datasource/common.ts +++ b/lib/datasource/common.ts @@ -50,6 +50,7 @@ export interface ReleaseResult { } export interface Datasource { + id: string; getDigest?(config: DigestConfig, newValue?: string): Promise<string | null>; getPkgReleases(config: PkgReleaseConfig): Promise<ReleaseResult | null>; } diff --git a/lib/datasource/index.spec.ts b/lib/datasource/index.spec.ts index 7f0a7ea63fd54d331c55a57544c36913c6c3eb14..717c6ad314ffc724d5ca062db0a0ff57a2880071 100644 --- a/lib/datasource/index.spec.ts +++ b/lib/datasource/index.spec.ts @@ -4,6 +4,7 @@ import * as datasourceDocker from './docker'; import * as datasourceGithubTags from './github-tags'; import * as datasourceNpm from './npm'; import { mocked } from '../../test/util'; +import { loadModules } from '../util/modules'; jest.mock('./docker'); jest.mock('./npm'); @@ -15,9 +16,32 @@ describe('datasource/index', () => { expect(datasource.getDatasources()).toBeDefined(); expect(datasource.getDatasourceList()).toBeDefined(); }); - it('returns if digests are supported', () => { + it('validates dataource', async () => { + function validateDatasource( + module: datasource.Datasource, + name: string + ): boolean { + if (!module.getPkgReleases) { + return false; + } + if (module.id !== name) { + return false; + } + return true; + } + const dss = datasource.getDatasources(); + + const loadedDs = loadModules(__dirname, validateDatasource); + expect(Array.from(dss.keys())).toEqual(Object.keys(loadedDs)); + + for (const dsName of dss.keys()) { + const ds = await dss.get(dsName); + expect(validateDatasource(ds, dsName)).toBe(true); + } + }); + it('returns if digests are supported', async () => { expect( - datasource.supportsDigests({ datasource: datasourceGithubTags.id }) + await datasource.supportsDigests({ datasource: datasourceGithubTags.id }) ).toBe(true); }); it('returns null for no datasource', async () => { diff --git a/lib/datasource/index.ts b/lib/datasource/index.ts index 62f675fc219ab91d579628a16655e770b3dde259..82b80a3631c0fed8b2c98bea9f396a97d00609b1 100644 --- a/lib/datasource/index.ts +++ b/lib/datasource/index.ts @@ -11,28 +11,20 @@ import { DigestConfig, } from './common'; import * as semverVersioning from '../versioning/semver'; -import { loadModules } from '../util/modules'; +import datasources from './api.generated'; export * from './common'; -// istanbul ignore next -function validateDatasource(module, name): boolean { - if (!module.getPkgReleases) { - return false; - } - if (module.id !== name) { - return false; - } - return true; -} - -const datasources = loadModules<Datasource>(__dirname, validateDatasource); -export const getDatasources = (): Record<string, Datasource> => datasources; -const datasourceList = Object.keys(datasources); -export const getDatasourceList = (): string[] => datasourceList; +export const getDatasources = (): Map<string, Promise<Datasource>> => + datasources; +export const getDatasourceList = (): string[] => Array.from(datasources.keys()); const cacheNamespace = 'datasource-releases'; +function load(datasource: string): Promise<Datasource> { + return datasources.get(datasource); +} + async function fetchReleases( config: PkgReleaseConfig ): Promise<ReleaseResult | null> { @@ -41,11 +33,11 @@ async function fetchReleases( logger.warn('No datasource found'); return null; } - if (!datasources[datasource]) { + if (!datasources.has(datasource)) { logger.warn('Unknown datasource: ' + datasource); return null; } - const dep = await datasources[datasource].getPkgReleases(config); + const dep = await (await load(datasource)).getPkgReleases(config); addMetaData(dep, datasource, config.lookupName); return dep; } @@ -75,7 +67,7 @@ export async function getPkgReleases( logger.error({ config }, 'Datasource getPkgReleases without lookupName'); return null; } - let res; + let res: ReleaseResult; try { res = await getRawReleases({ ...config, @@ -107,17 +99,17 @@ export async function getPkgReleases( return res; } -export function supportsDigests(config: DigestConfig): boolean { - return 'getDigest' in datasources[config.datasource]; +export async function supportsDigests(config: DigestConfig): Promise<boolean> { + return 'getDigest' in (await load(config.datasource)); } -export function getDigest( +export async function getDigest( config: DigestConfig, value?: string ): Promise<string | null> { const lookupName = config.lookupName || config.depName; const { registryUrls } = config; - return datasources[config.datasource].getDigest( + return (await load(config.datasource)).getDigest( { lookupName, registryUrls }, value ); diff --git a/lib/workers/repository/process/lookup/index.ts b/lib/workers/repository/process/lookup/index.ts index 5b24ec1233eaea70287c01067d0e9d1ee77e10f7..dd9a154fc0966215a7cda788b1d28c7d8498f9c6 100644 --- a/lib/workers/repository/process/lookup/index.ts +++ b/lib/workers/repository/process/lookup/index.ts @@ -357,7 +357,7 @@ export async function lookupUpdates( } } // Add digests if necessary - if (supportsDigests(config)) { + if (await supportsDigests(config)) { if ( config.currentDigest && config.datasource !== datasourceGitSubmodules.id diff --git a/package.json b/package.json index b70bb4f9b034b521f84b0a4fb42f0c3d74088ee7..d1ad57aab8098c6fac935578b1eba098e77d64c4 100644 --- a/package.json +++ b/package.json @@ -7,12 +7,14 @@ "renovate-config-validator": "bin/config-validator.js" }, "scripts": { - "build": "run-s clean compile:* copy-static-files", - "build:docker": "run-s compile:ts copy-static-files", - "clean": "rimraf dist", + "build": "run-s clean generate:* compile:* copy-static-files", + "build:docker": "run-s generate:* compile:ts copy-static-files", + "clean": "rimraf dist lib/**/*.generated.ts", "clean-cache": "node bin/clean-cache.js", "compile:ts": "tsc -p tsconfig.app.json", "compile:dts": "tsc -p tsconfig.dts.json", + "generate": "run-s generate:*", + "generate:imports": "node --experimental-modules tools/generate-imports.mjs", "copy-static-files": "copyfiles -u 1 -e **/__fixtures__/** -e **/__mocks__/** lib/**/*.json lib/**/*.py dist/", "create-json-schema": "babel-node --extensions \".ts,.js\" -- bin/create-json-schema.js && prettier --write \"renovate-schema.json\"", "debug": "babel-node --inspect-brk --extensions \".ts,.js\" -- lib/renovate.ts", @@ -26,6 +28,9 @@ "prepare": "run-s prepare:*", "prepare:jest": "node --experimental-modules tools/patch-jest.mjs", "prepare:re2": "node --experimental-modules tools/check-re2.mjs", + "prepare:generate": "run-s generate:*", + "prestart": "run-s generate:* ", + "pretest": "run-s generate:* ", "prettier": "prettier --list-different \"**/*.{ts,js,mjs,json,md}\"", "prettier-fix": "prettier --write \"**/*.{ts,js,mjs,json,md}\"", "release": "run-s \"release:* {@}\" --", @@ -36,7 +41,8 @@ "test-e2e": "npm pack && cd e2e && yarn install --no-lockfile --ignore-optional --prod && yarn test", "test-schema": "babel-node --extensions \".ts,.js\" -- test/json-schema.ts", "test": "run-s lint test-schema type-check jest", - "type-check": "tsc --noEmit" + "tsc": "tsc", + "type-check": "run-s generate:* \"tsc --noEmit\"" }, "repository": { "type": "git", diff --git a/tools/generate-imports.mjs b/tools/generate-imports.mjs new file mode 100644 index 0000000000000000000000000000000000000000..df0b07fca70ad526d0af898f7b186cb040bbc8ff --- /dev/null +++ b/tools/generate-imports.mjs @@ -0,0 +1,43 @@ +import shell from 'shelljs'; +import fs from 'fs-extra'; + +shell.echo('generating imports'); +const newFiles = new Set(); + +if (!fs.existsSync('lib')) { + shell.echo('> missing sources'); + shell.exit(0); +} + +function findModules(dirname) { + return fs + .readdirSync(dirname, { withFileTypes: true }) + .filter(dirent => dirent.isDirectory()) + .map(dirent => dirent.name) + .filter(name => !name.startsWith('__')) + .sort(); +} +function updateFile(file, code) { + const oldCode = fs.existsSync(file) ? fs.readFileSync(file) : null; + if (code !== oldCode) { + fs.writeFileSync(file, code); + } + newFiles.add(file); +} + +let code = ` +import { Datasource } from './common'; +const api = new Map<string, Promise<Datasource>>(); +export default api; +`; +for (const ds of findModules('lib/datasource')) { + code += `api.set('${ds}', import('./${ds}'));\n`; +} + +updateFile('lib/datasource/api.generated.ts', code); + +for (const file of shell + .find('lib/**/*.generated.ts') + .filter(f => !newFiles.has(f))) { + fs.removeSync(file); +} diff --git a/tools/package.json b/tools/package.json index 41853d2eb18a6755c5476fe6757f1ad89363da56..0b98cf51cfa9ec82ce36de1a287b416f7752971c 100644 --- a/tools/package.json +++ b/tools/package.json @@ -1,8 +1,9 @@ { - "private":true, + "private": true, "type": "module", "dependencies": { "commander": "4.1.1", + "fs-extra": "8.1.0", "got": "9.6.0", "shelljs": "0.8.3" } diff --git a/tsconfig.app.json b/tsconfig.app.json index 25e6cee568675a91d5d7d635ad4129162b505074..9802bf51a91005edd903da718fac2254ce398159 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -5,8 +5,16 @@ "isolatedModules": true, "sourceMap": true, "inlineSources": true, + "allowJs": false, + "checkJs": false, "types": ["node"] }, - "include": ["lib/**/*"], - "exclude": ["lib/**/*.spec.ts", "**/__mocks__/**", "**/__fixtures__/**"] + "exclude": [ + "./.cache", + "./dist", + "**/__mocks__/**", + "**/__fixtures__/**", + "**/*.spec.ts", + "./test" + ] } diff --git a/tsconfig.json b/tsconfig.json index 3517c16fd7dd78a7107d76993a0ca4baf4a39b4f..a7e3021387312e713072bc7657502f7a5b19d971 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,8 +12,17 @@ "resolveJsonModule": false, "isolatedModules": true, "lib": ["es2018"], - "types": ["node", "jest"] + "types": ["node", "jest"], + "allowJs": true, + "checkJs": true }, - "include": ["lib/**/*", "test/**/*", "tools/**/*.mjs"], - "exclude": ["node_modules", "./.cache", "./dist", "**/__mocks__/*"] + "exclude": [ + "node_modules", + "./.cache", + "./dist", + "**/__mocks__/*", + "bin", + "coverage", + "config.js" + ] } diff --git a/tsconfig.lint.json b/tsconfig.lint.json new file mode 100644 index 0000000000000000000000000000000000000000..cb284e39d461741e4d89ab496609e26a125968e7 --- /dev/null +++ b/tsconfig.lint.json @@ -0,0 +1,4 @@ +{ + "extends": "./tsconfig", + "include": ["**/*.ts", "**/*.js", "**/*.mjs"] +}