diff --git a/bin/create-json-schema.js b/bin/create-json-schema.js
deleted file mode 100644
index d30f6adb99cf6598ba626973f55db8e2373c980c..0000000000000000000000000000000000000000
--- a/bin/create-json-schema.js
+++ /dev/null
@@ -1,111 +0,0 @@
-const fs = require('fs');
-const upath = require('upath');
-const { getOptions } = require('../lib/config/options');
-
-const schema = {
-  title: 'JSON schema for Renovate config files (https://renovatebot.com/)',
-  $schema: 'http://json-schema.org/draft-04/schema#',
-  type: 'object',
-  properties: {},
-};
-const options = getOptions();
-options.sort((a, b) => {
-  if (a.name < b.name) {
-    return -1;
-  }
-  if (a.name > b.name) {
-    return 1;
-  }
-  return 0;
-});
-const properties = {};
-
-function createSingleConfig(option) {
-  const temp = {};
-  if (option.description) {
-    temp.description = option.description;
-  }
-  temp.type = option.type;
-  if (temp.type === 'array') {
-    if (option.subType) {
-      temp.items = {
-        type: option.subType,
-      };
-      if (option.format) {
-        temp.items.format = option.format;
-      }
-      if (option.allowedValues) {
-        temp.items.enum = option.allowedValues;
-      }
-    }
-    if (option.subType === 'string' && option.allowString === true) {
-      const items = temp.items;
-      delete temp.items;
-      delete temp.type;
-      temp.oneOf = [{ type: 'array', items }, { ...items }];
-    }
-  } else {
-    if (option.format) {
-      temp.format = option.format;
-    }
-    if (option.allowedValues) {
-      temp.enum = option.allowedValues;
-    }
-  }
-  if (option.default !== undefined) {
-    temp.default = option.default;
-  }
-  if (option.additionalProperties !== undefined) {
-    temp.additionalProperties = option.additionalProperties;
-  }
-  if (temp.type === 'object' && !option.freeChoice) {
-    temp.$ref = '#';
-  }
-  return temp;
-}
-
-function createSchemaForParentConfigs() {
-  for (const option of options) {
-    if (!option.parent) {
-      properties[option.name] = createSingleConfig(option);
-    }
-  }
-}
-
-function addChildrenArrayInParents() {
-  for (const option of options) {
-    if (option.parent) {
-      properties[option.parent].items = {
-        allOf: [
-          {
-            type: 'object',
-            properties: {},
-          },
-        ],
-      };
-    }
-  }
-}
-
-function createSchemaForChildConfigs() {
-  for (const option of options) {
-    if (option.parent) {
-      properties[option.parent].items.allOf[0].properties[option.name] =
-        createSingleConfig(option);
-    }
-  }
-}
-
-function generateSchema() {
-  createSchemaForParentConfigs();
-  addChildrenArrayInParents();
-  createSchemaForChildConfigs();
-  schema.properties = properties;
-  fs.writeFileSync(
-    upath.join(__dirname, '../renovate-schema.json'),
-    JSON.stringify(schema, null, 2),
-    'utf-8'
-  );
-}
-
-generateSchema();
diff --git a/package.json b/package.json
index a2abd0018d5784c7112566d96407d6346ec5c1aa..297a02aefb3988896d8e3038d2c0747490f61640 100644
--- a/package.json
+++ b/package.json
@@ -8,12 +8,12 @@
   },
   "scripts": {
     "build": "run-s clean generate:* compile:*",
-    "build:docs": "run-s \"release:prepare {@}\"",
+    "build:docs": "run-s \"release:prepare {@}\" --",
     "clean": "rimraf dist tmp",
-    "clean-cache": "node bin/clean-cache.mjs",
+    "clean-cache": "node tools/clean-cache.mjs",
     "compile:ts": "tsc -p tsconfig.app.json",
     "config-validator": "node -r ts-node/register/transpile-only -- lib/config-validator.ts",
-    "create-json-schema": "node -r ts-node/register/transpile-only -- bin/create-json-schema.js && prettier --write \"renovate-schema.json\"",
+    "create-json-schema": "node -r ts-node/register/transpile-only -- tools/generate-schema.ts && prettier --write \"renovate-schema.json\"",
     "debug": "node --inspect-brk -r ts-node/register/transpile-only  -- lib/renovate.ts",
     "doc-fix": "run-s markdown-lint-fix prettier-fix",
     "doc-fence-check": "node tools/check-fenced-code.mjs",
@@ -30,7 +30,7 @@
     "ls-lint": "ls-lint",
     "markdown-lint": "markdownlint-cli2",
     "markdown-lint-fix": "markdownlint-cli2-fix",
-    "null-check": "run-s generate:* \"tsc --noEmit -p tsconfig.strict.json {@}\"",
+    "null-check": "run-s generate:* \"tsc --noEmit -p tsconfig.strict.json {@}\" --",
     "prepare": "run-s prepare:*",
     "prepare:husky": "husky install",
     "prepare:generate": "run-s generate:*",
@@ -45,9 +45,9 @@
     "test": "run-s lint test-schema type-check null-check jest",
     "test-dirty": "git diff --exit-code",
     "test-e2e": "npm pack && cd test/e2e && yarn install --no-lockfile --ignore-optional --prod && yarn test",
-    "test-schema": "node -r ts-node/register/transpile-only -- test/json-schema.ts",
+    "test-schema": "run-s create-json-schema",
     "tsc": "tsc",
-    "type-check": "run-s generate:* \"tsc --noEmit {@}\"",
+    "type-check": "run-s generate:* \"tsc --noEmit {@}\" --",
     "verify": "node tools/verify.mjs"
   },
   "repository": {
diff --git a/test/json-schema.ts b/test/json-schema.ts
deleted file mode 100644
index 0726145597c79660053c69074624a9f95349b6cb..0000000000000000000000000000000000000000
--- a/test/json-schema.ts
+++ /dev/null
@@ -1,3 +0,0 @@
-import shell from 'shelljs';
-
-shell.exec('yarn create-json-schema');
diff --git a/tools/check-fenced-code.mjs b/tools/check-fenced-code.mjs
index 5d90c7f28aed8cf95013c9edde56986b4e469e17..dedb3c155da88b776f655db1c094e6f902b3ba32 100644
--- a/tools/check-fenced-code.mjs
+++ b/tools/check-fenced-code.mjs
@@ -16,9 +16,14 @@ let issues = 0;
 
 markdown.enable(['fence']);
 
+/**
+ *
+ * @param {string} file
+ * @param {import('markdown-it/lib/token')} token
+ */
 function checkValidJson(file, token) {
-  const start = parseInt(token.map[0], 10) + 1;
-  const end = parseInt(token.map[1], 10) + 1;
+  const start = token.map ? token.map[0] + 1 : 0;
+  const end = token.map ? token.map[1] + 1 : 0;
 
   try {
     JSON.parse(token.content);
@@ -36,6 +41,10 @@ function checkValidJson(file, token) {
   }
 }
 
+/**
+ *
+ * @param {string} file
+ */
 async function processFile(file) {
   const text = await fs.readFile(file, 'utf8');
   const tokens = markdown.parse(text, undefined);
diff --git a/tools/check-git-version.mjs b/tools/check-git-version.mjs
index 65b006acbefd85cab6fdb0925a412c398c6d8a03..2f9be9fcf33d473947710bad44eeb3768837231c 100644
--- a/tools/check-git-version.mjs
+++ b/tools/check-git-version.mjs
@@ -9,15 +9,15 @@ const git = simpleGit();
   try {
     const regex = /\d+\.\d+\.\d+/;
     const stdout = await git.raw('--version');
-    const [gitVersion] = regex.exec(stdout);
-    if (semver.lt(gitVersion, GIT_MINIMUM_VERSION)) {
+    const [gitVersion] = regex.exec(stdout) ?? [];
+    if (!gitVersion || semver.lt(gitVersion, GIT_MINIMUM_VERSION)) {
       if (process.env.CI) {
         shell.echo(
-          `::error ::Minimum Git version ${GIT_MINIMUM_VERSION} is required`
+          `::error ::Minimum Git version ${GIT_MINIMUM_VERSION} is required, found version '${gitVersion}'.`
         );
       } else {
         throw new Error(
-          `Minimum Git version ${GIT_MINIMUM_VERSION} is required`
+          `Minimum Git version ${GIT_MINIMUM_VERSION} is required, found version '${gitVersion}'.`
         );
       }
     }
diff --git a/bin/clean-cache.mjs b/tools/clean-cache.mjs
similarity index 85%
rename from bin/clean-cache.mjs
rename to tools/clean-cache.mjs
index 38a65f5476bad87f61773bf651622c8e94574fd5..89d1d1c67928b7560b05feb8721e0c700aef0a38 100644
--- a/bin/clean-cache.mjs
+++ b/tools/clean-cache.mjs
@@ -4,7 +4,7 @@ import { remove } from 'fs-extra';
 
 // eslint-disable-next-line @typescript-eslint/no-floating-promises
 (async () => {
-  const tmpDir = process.env.RENOVATE_TMPDIR || tmpdir();
+  const tmpDir = process.env.RENOVATE_TMPDIR ?? tmpdir();
   const renovateDir = join(tmpDir, 'renovate');
   // eslint-disable-next-line no-console
   console.log('Removing ' + renovateDir);
diff --git a/tools/docs/manager.ts b/tools/docs/manager.ts
index 3b1a02f548ddc85f99eb425d39ac5174e479f6c0..52985395dcd0b1e0c0913c622e7518392ccc6476 100644
--- a/tools/docs/manager.ts
+++ b/tools/docs/manager.ts
@@ -18,7 +18,7 @@ export async function generateManagers(dist: string): Promise<void> {
   const managers = getManagers();
   const allLanguages: Record<string, string[]> = {};
   for (const [manager, definition] of managers) {
-    const language = definition.language || 'other';
+    const language = definition.language ?? 'other';
     allLanguages[language] = allLanguages[language] || [];
     allLanguages[language].push(manager);
     const { defaultConfig, supportedDatasources } = definition;
diff --git a/tools/docs/schema.ts b/tools/docs/schema.ts
index 8576e14d0fbfc2693b344d6fd158893df2936393..abc3c6cbb133dbd79f7f73fe40d8ddb8a25f042a 100644
--- a/tools/docs/schema.ts
+++ b/tools/docs/schema.ts
@@ -22,7 +22,7 @@ options.sort((a, b) => {
 const properties = schema.properties as Record<string, any>;
 
 function createSingleConfig(option: RenovateOptions): Record<string, unknown> {
-  const temp = {} as Record<string, any> & RenovateOptions;
+  const temp: Record<string, any> & Partial<RenovateOptions> = {};
   if (option.description) {
     temp.description = option.description;
   }
diff --git a/tools/docs/utils.ts b/tools/docs/utils.ts
index 7d8060a30ea71cd844561853707eb5a49e2d904e..671a0f3f56eb82ef84fcfdeb37502116967347e3 100644
--- a/tools/docs/utils.ts
+++ b/tools/docs/utils.ts
@@ -19,7 +19,7 @@ export function getDisplayName(
   moduleName: string,
   moduleDefinition: ModuleApi
 ): string {
-  return moduleDefinition.displayName || formatName(moduleName);
+  return moduleDefinition.displayName ?? formatName(moduleName);
 }
 
 export function getNameWithUrl(
diff --git a/tools/docs/versioning.ts b/tools/docs/versioning.ts
index 368d07c1a53e527bd8f8052e7a642a049ac0253e..6a4bf97a37ad5555aea4c8188a9a33fa5057a839 100644
--- a/tools/docs/versioning.ts
+++ b/tools/docs/versioning.ts
@@ -28,7 +28,7 @@ export async function generateVersioning(dist: string): Promise<void> {
     versioningContent += `**Ranges/Constraints:**\n\n`;
     if (supportsRanges) {
       versioningContent += `✅ Ranges are supported.\n\nValid \`rangeStrategy\` values are: ${(
-        supportedRangeStrategies || []
+        supportedRangeStrategies ?? []
       )
         .map((strategy: string) => `\`${strategy}\``)
         .join(', ')}\n\n`;
diff --git a/tools/generate-schema.ts b/tools/generate-schema.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fb7e3c27df5e92eda8ca33a309935be5548afd5e
--- /dev/null
+++ b/tools/generate-schema.ts
@@ -0,0 +1,28 @@
+import { ERROR } from 'bunyan';
+import shell from 'shelljs';
+import { getProblems, logger } from '../lib/logger';
+import { generateSchema } from './docs/schema';
+
+process.on('unhandledRejection', (err) => {
+  // Will print "unhandledRejection err is not defined"
+  logger.error({ err }, 'unhandledRejection');
+  process.exit(-1);
+});
+
+// eslint-disable-next-line @typescript-eslint/no-floating-promises
+(async () => {
+  try {
+    const dist = '.';
+
+    // json-schema
+    logger.info('Generating json-schema');
+    await generateSchema(dist);
+  } catch (err) {
+    logger.error({ err }, 'Unexpected error');
+  } finally {
+    const loggerErrors = getProblems().filter((p) => p.level >= ERROR);
+    if (loggerErrors.length) {
+      shell.exit(1);
+    }
+  }
+})();
diff --git a/tools/utils/index.ts b/tools/utils/index.ts
index d17bf464ec047cbd5ac540544b6344234875df94..ec30b693adf2ed056d98f7eb44e1d97d85a2f62d 100644
--- a/tools/utils/index.ts
+++ b/tools/utils/index.ts
@@ -10,7 +10,7 @@ export const newFiles = new Set();
  * @returns {string}
  */
 export function getEnv(key: string): string {
-  return process.env[key] || '';
+  return process.env[key] ?? '';
 }
 
 /**
diff --git a/tsconfig.json b/tsconfig.json
index abffd849532bfb67e3d4deb5ce387bb831f2c7f0..c6ffee806cd56de50ae4e69fc1c9bf216106bdd1 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,7 @@
     "checkJs": true,
     "importsNotUsedAsValues": "error"
   },
+  "include": ["**/*.ts", "**/*.js", "**/*.mjs", "**/*.cjs"],
   "exclude": [
     "node_modules",
     "./.cache",
diff --git a/tsconfig.strict.json b/tsconfig.strict.json
index e5591d0e6d224d02d7a0fe6058d44c956ced2785..b94fc530cb164f4a0310c4d868215a85d2ca9417 100644
--- a/tsconfig.strict.json
+++ b/tsconfig.strict.json
@@ -474,10 +474,6 @@
     "test/setup.ts",
     "test/to-migrate.ts",
     "test/util.ts",
-    "tools/check-fenced-code.mjs",
-    "tools/check-git-version.mjs",
-    "tools/check-re2.mjs",
-    "tools/dispatch-release.mjs",
     "tools/docs/config.ts",
     "tools/docs/datasources.ts",
     "tools/docs/manager.ts",
@@ -489,9 +485,6 @@
     "tools/docs/utils.ts",
     "tools/docs/versioning.ts",
     "tools/generate-docs.ts",
-    "tools/generate-imports.mjs",
-    "tools/release.mjs",
-    "tools/utils.mjs",
-    "tools/verify.mjs"
+    "tools/generate-schema.ts"
   ]
 }