diff --git a/lib/config/decrypt/openpgp.spec.ts b/lib/config/decrypt/openpgp.spec.ts
index 3b5cc11800cffdf6a630ad68355a1ecc41af90a5..6fa7515ccd16b76dc1bfdcaefa9c6e9073e30ffe 100644
--- a/lib/config/decrypt/openpgp.spec.ts
+++ b/lib/config/decrypt/openpgp.spec.ts
@@ -130,8 +130,8 @@ describe('config/decrypt/openpgp', () => {
           throw new Error('openpgp error');
         },
       }));
-      const pgp = await import('./openpgp');
-      const { logger } = await import('../../logger');
+      const pgp = await import('./openpgp.js');
+      const { logger } = await import('../../logger/index.js');
       expect(
         await pgp.tryDecryptOpenPgp(
           '',
diff --git a/lib/config/index.spec.ts b/lib/config/index.spec.ts
index a748d8066ec03f605d19f9bfdfa122811e4acc2a..f0a9f0b2d9afe9e114462cb81b613eaeef20b039 100644
--- a/lib/config/index.spec.ts
+++ b/lib/config/index.spec.ts
@@ -97,7 +97,7 @@ describe('config/index', () => {
       const childConfig = {
         packageRules: [{ a: 3 }, { a: 4 }],
       };
-      const configParser = await import('./index');
+      const configParser = await import('./index.js');
       const config = configParser.mergeChildConfig(parentConfig, childConfig);
       expect(config.packageRules).toHaveLength(2);
     });
diff --git a/lib/modules/manager/npm/update/package-version/index.spec.ts b/lib/modules/manager/npm/update/package-version/index.spec.ts
index b18f568cad002068cdd2128c98cbec9d3a585884..86bb149dbd9df26ee020aadaf2cc72b632edc578 100644
--- a/lib/modules/manager/npm/update/package-version/index.spec.ts
+++ b/lib/modules/manager/npm/update/package-version/index.spec.ts
@@ -62,7 +62,7 @@ describe('modules/manager/npm/update/package-version/index', () => {
           throw new Error('semver inc');
         },
       }));
-      const npmUpdater1 = await import('.');
+      const npmUpdater1 = await import('./index.js');
       const { bumpedContent } = npmUpdater1.bumpPackageVersion(
         content,
         '0.0.2',
diff --git a/lib/modules/platform/azure/azure-got-wrapper.spec.ts b/lib/modules/platform/azure/azure-got-wrapper.spec.ts
index 0c9522619d55650d9ee4013f78c0f7956aa1c4c3..62afafa62311f42f8243862d598cbb025de96b4d 100644
--- a/lib/modules/platform/azure/azure-got-wrapper.spec.ts
+++ b/lib/modules/platform/azure/azure-got-wrapper.spec.ts
@@ -7,8 +7,8 @@ describe('modules/platform/azure/azure-got-wrapper', () => {
   beforeEach(async () => {
     // reset module
     vi.resetModules();
-    hostRules = await import('../../../util/host-rules');
-    azure = await import('./azure-got-wrapper');
+    hostRules = await vi.importActual('../../../util/host-rules');
+    azure = await vi.importActual('./azure-got-wrapper');
   });
 
   describe('gitApi', () => {
diff --git a/lib/modules/platform/azure/azure-helper.spec.ts b/lib/modules/platform/azure/azure-helper.spec.ts
index 5bb19fb44d705c9dbbd24b75729d5a4cf0ce1587..81081a7210e375a82f7bfb3a5e0132c4f54d22fe 100644
--- a/lib/modules/platform/azure/azure-helper.spec.ts
+++ b/lib/modules/platform/azure/azure-helper.spec.ts
@@ -15,7 +15,7 @@ describe('modules/platform/azure/azure-helper', () => {
   beforeEach(async () => {
     // reset module
     vi.resetModules();
-    azureHelper = await import('./azure-helper');
+    azureHelper = await vi.importActual('./azure-helper');
     azureApi = await vi.importMock('./azure-got-wrapper');
   });
 
diff --git a/lib/modules/platform/azure/index.spec.ts b/lib/modules/platform/azure/index.spec.ts
index e607afd613073bbd0edbef042e8b99252b3c7c8d..e51be7a86f97232f926f4bb3812833166371eed6 100644
--- a/lib/modules/platform/azure/index.spec.ts
+++ b/lib/modules/platform/azure/index.spec.ts
@@ -42,10 +42,12 @@ describe('modules/platform/azure/index', () => {
     // reset module
     vi.resetModules();
     hostRules = await vi.importMock('../../../util/host-rules');
-    azure = await import('.');
+    azure = await vi.importActual('.');
     azureApi = await vi.importMock('./azure-got-wrapper');
     azureHelper = await vi.importMock('./azure-helper');
-    logger = vi.mocked(await import('../../../logger'), true).logger;
+    logger = (
+      await vi.importMock<typeof import('../../../logger')>('../../../logger')
+    ).logger;
     git = await vi.importMock('../../../util/git');
     git.branchExists.mockReturnValue(true);
     git.isBranchBehindBase.mockResolvedValue(false);
diff --git a/lib/modules/platform/codecommit/index.spec.ts b/lib/modules/platform/codecommit/index.spec.ts
index 4c9fbc104ccfff52b0294f770733a0c05e19fae2..69da1cee1c611ae1b8020fa78b398035a53230d7 100644
--- a/lib/modules/platform/codecommit/index.spec.ts
+++ b/lib/modules/platform/codecommit/index.spec.ts
@@ -36,7 +36,7 @@ describe('modules/platform/codecommit/index', () => {
   let codeCommit: Platform;
 
   beforeAll(async () => {
-    codeCommit = await import('.');
+    codeCommit = await vi.importActual('.');
     await codeCommit.initPlatform({
       endpoint: 'https://git-codecommit.eu-central-1.amazonaws.com/',
       username: 'accessKeyId',
diff --git a/lib/renovate.spec.ts b/lib/renovate.spec.ts
index b0d3129daa78fa04eae528d789a22d324d10b40c..fad038ddf881090d51c47109b8ec44ae81e9a6b3 100644
--- a/lib/renovate.spec.ts
+++ b/lib/renovate.spec.ts
@@ -4,7 +4,7 @@ Object.defineProperty(renovateWorker, 'start', { value: vi.fn() });
 
 describe('renovate', () => {
   it('starts', async () => {
-    await import('./renovate');
+    await vi.importActual('./renovate');
     expect(renovateWorker.start).toHaveBeenCalledTimes(1);
   });
 });
diff --git a/lib/util/exec/containerbase.ts b/lib/util/exec/containerbase.ts
index af101dde4bf2659db12c3cf3d058abeead3c64dc..6220ab61809bc74be0ee268f93ff1fd2a68f20d1 100644
--- a/lib/util/exec/containerbase.ts
+++ b/lib/util/exec/containerbase.ts
@@ -230,7 +230,7 @@ async function getPkgReleases(
   toolConfig: ToolConfig,
 ): Promise<ReleaseResult | null> {
   if (_getPkgReleases === null) {
-    _getPkgReleases = import('../../modules/datasource');
+    _getPkgReleases = import('../../modules/datasource/index.js');
   }
   const { getPkgReleases } = await _getPkgReleases;
   return getPkgReleases(toolConfig);
diff --git a/lib/util/regex.spec.ts b/lib/util/regex.spec.ts
index b741eb13de48dd1fb13e288a0d1b4e4753ba2129..0c6ff54b2bf8fd735b90e2741b7a996bdc9aacc4 100644
--- a/lib/util/regex.spec.ts
+++ b/lib/util/regex.spec.ts
@@ -33,7 +33,7 @@ describe('util/regex', () => {
       },
     }));
 
-    const regex = await import('./regex');
+    const regex = await import('./regex.js');
     expect(regex.regEx('foo')).toBeInstanceOf(RegExp);
   });
 });
diff --git a/lib/util/s3.spec.ts b/lib/util/s3.spec.ts
index 9695747b491fe29c6e7403362dccb48ab5fd5185..c0cea731fa47cb7c1d2bc461860f69de227452c0 100644
--- a/lib/util/s3.spec.ts
+++ b/lib/util/s3.spec.ts
@@ -27,8 +27,8 @@ describe('util/s3', () => {
   });
 
   it('uses user-configured s3 values', async () => {
-    const s3 = await import('./s3');
-    const globalConfig = await import('../config/global');
+    const s3 = await import('./s3.js');
+    const globalConfig = await import('../config/global.js');
     globalConfig.GlobalConfig.set({
       s3Endpoint: 'https://minio.domain.test',
       s3PathStyle: true,
@@ -47,7 +47,7 @@ describe('util/s3', () => {
   });
 
   it('uses s3 values from globalConfig instead of GlobalConfig class', async () => {
-    const s3 = await import('./s3');
+    const s3 = await import('./s3.js');
     const client1 = s3.getS3Client('https://minio.domain.test', true);
     const client2 = getS3Client('https://minio.domain.test', true);
     expect(client1).not.toBe(client2);
diff --git a/lib/workers/global/config/parse/index.spec.ts b/lib/workers/global/config/parse/index.spec.ts
index ae4f375c01f13d7cbf462d8fe279f19e5de93bf7..b15ec81e494c80f34b8240153d8a13c95afce21b 100644
--- a/lib/workers/global/config/parse/index.spec.ts
+++ b/lib/workers/global/config/parse/index.spec.ts
@@ -17,7 +17,7 @@ describe('workers/global/config/parse/index', () => {
     let defaultEnv: NodeJS.ProcessEnv;
 
     beforeEach(async () => {
-      configParser = await import('./index');
+      configParser = await vi.importActual('./index');
       defaultArgv = getArgv();
       defaultEnv = {
         RENOVATE_CONFIG_FILE: upath.resolve(
diff --git a/package.json b/package.json
index dd2e6b5e2348f604762a212adef6fd7c433cdfe2..4c2294a0688f88a4fcbfa5c1b04495070499efd1 100644
--- a/package.json
+++ b/package.json
@@ -9,14 +9,14 @@
   },
   "scripts": {
     "build": "run-s clean 'generate:*' 'compile:*' create-json-schema",
-    "build:docker": "ts-node tools/docker.ts",
-    "build:docs": "ts-node tools/generate-docs.ts",
+    "build:docker": "tsx tools/docker.ts",
+    "build:docs": "tsx tools/generate-docs.ts",
     "clean": "rimraf dist tmp",
     "clean-cache": "node tools/clean-cache.mjs",
     "compile:ts": "tsc -p tsconfig.app.json",
-    "config-validator": "ts-node lib/config-validator.ts",
-    "create-json-schema": "ts-node tools/generate-schema.ts && prettier --write --cache 'renovate-schema.json'",
-    "debug": "NODE_OPTIONS=--inspect-brk ts-node lib/renovate.ts",
+    "config-validator": "tsx lib/config-validator.ts",
+    "create-json-schema": "tsx tools/generate-schema.ts && prettier --write --cache 'renovate-schema.json'",
+    "debug": "NODE_OPTIONS=--inspect-brk tsx lib/renovate.ts",
     "doc-fix": "run-s markdown-lint-fix prettier-fix",
     "doc-fix-everything": "run-s doc-fix doc-fence-check lint-documentation",
     "doc-fence-check": "node tools/check-fenced-code.mjs",
@@ -35,7 +35,7 @@
     "ls-lint": "ls-lint",
     "markdown-lint": "markdownlint-cli2",
     "markdown-lint-fix": "markdownlint-cli2 --fix",
-    "mkdocs": "ts-node tools/mkdocs.ts",
+    "mkdocs": "tsx tools/mkdocs.ts",
     "prepare": "run-s 'prepare:*'",
     "prepare:husky": "husky",
     "prepare:generate": "run-s 'generate:*'",
@@ -44,9 +44,9 @@
     "pretest": "run-s 'generate:*'",
     "prettier": "prettier --cache --check '**/*.{ts,js,mjs,json,md,yml}'",
     "prettier-fix": "prettier --write --cache '**/*.{ts,js,mjs,json,md,yml}'",
-    "release:prepare": "ts-node tools/prepare-release.ts",
-    "release:publish": "ts-node tools/publish-release.ts",
-    "start": "ts-node lib/renovate.ts",
+    "release:prepare": "tsx tools/prepare-release.ts",
+    "release:publish": "tsx tools/publish-release.ts",
+    "start": "tsx lib/renovate.ts",
     "test": "run-s lint test-schema jest",
     "test-dirty": "git diff --exit-code",
     "test-e2e": "run-s 'test-e2e:*'",
@@ -55,7 +55,7 @@
     "test-e2e:run": "cd test/e2e && npm test",
     "test-schema": "run-s create-json-schema",
     "test:docs": "node --test tools/docs/test/**/*.mjs",
-    "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true ts-node tools/test-shards.ts",
+    "schedule-test-shards": "SCHEDULE_TEST_SHARDS=true tsx tools/test-shards.ts",
     "tsc": "tsc",
     "type-check": "run-s 'generate:*' 'tsc --noEmit {@}' --",
     "update-static-data": "run-s 'update-static-data:*'",
@@ -338,7 +338,7 @@
     "semantic-release": "24.2.3",
     "tar": "7.4.3",
     "tmp-promise": "3.0.3",
-    "ts-node": "10.9.2",
+    "tsx": "4.19.3",
     "type-fest": "4.35.0",
     "typescript": "5.8.2",
     "typescript-eslint": "8.26.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 3a7fed12d29a395f1cedc754b358cd6c51bae1d0..fec895531dab4d5f92e791dc8a47ee6d89bd95f2 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -490,10 +490,10 @@ importers:
         version: 1.1.9
       '@vitest/coverage-v8':
         specifier: 3.0.7
-        version: 3.0.7(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0))
+        version: 3.0.7(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))
       '@vitest/eslint-plugin':
         specifier: 1.1.31
-        version: 1.1.31(@typescript-eslint/utils@8.26.0(eslint@9.21.0)(typescript@5.8.2))(eslint@9.21.0)(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0))
+        version: 1.1.31(@typescript-eslint/utils@8.26.0(eslint@9.21.0)(typescript@5.8.2))(eslint@9.21.0)(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))
       aws-sdk-client-mock:
         specifier: 4.1.0
         version: 4.1.0
@@ -575,9 +575,9 @@ importers:
       tmp-promise:
         specifier: 3.0.3
         version: 3.0.3
-      ts-node:
-        specifier: 10.9.2
-        version: 10.9.2(@types/node@22.13.5)(typescript@5.8.2)
+      tsx:
+        specifier: 4.19.3
+        version: 4.19.3
       type-fest:
         specifier: 4.35.0
         version: 4.35.0
@@ -592,16 +592,16 @@ importers:
         version: 9.2.2
       vite:
         specifier: 6.2.0
-        version: 6.2.0(@types/node@22.13.5)(yaml@2.7.0)
+        version: 6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
       vite-tsconfig-paths:
         specifier: 5.1.4
-        version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.5)(yaml@2.7.0))
+        version: 5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))
       vitest:
         specifier: 3.0.7
-        version: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0)
+        version: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
       vitest-mock-extended:
         specifier: 3.0.1
-        version: 3.0.1(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0))
+        version: 3.0.1(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))
     optionalDependencies:
       better-sqlite3:
         specifier: 11.8.1
@@ -1350,7 +1350,6 @@ packages:
 
   '@ls-lint/ls-lint@2.2.3':
     resolution: {integrity: sha512-ekM12jNm/7O2I/hsRv9HvYkRdfrHpiV1epVuI2NP+eTIcEgdIdKkKCs9KgQydu/8R5YXTov9aHdOgplmCHLupw==}
-    cpu: [x64, arm64, s390x]
     os: [darwin, linux, win32]
     hasBin: true
 
@@ -6340,6 +6339,11 @@ packages:
   tslib@2.8.1:
     resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
 
+  tsx@4.19.3:
+    resolution: {integrity: sha512-4H8vUNGNjQ4V2EOoGw005+c+dGuPSnhpPBPHBtsZdGZBk/iJb4kguGlPWaZTZ3q5nMtFOEsY0nRDlh9PJyd6SQ==}
+    engines: {node: '>=18.0.0'}
+    hasBin: true
+
   tunnel-agent@0.6.0:
     resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==}
 
@@ -7816,6 +7820,7 @@ snapshots:
   '@cspotcode/source-map-support@0.8.1':
     dependencies:
       '@jridgewell/trace-mapping': 0.3.9
+    optional: true
 
   '@esbuild/aix-ppc64@0.25.0':
     optional: true
@@ -8174,6 +8179,7 @@ snapshots:
     dependencies:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.5.0
+    optional: true
 
   '@jsonjoy.com/base64@1.1.2(tslib@2.8.1)':
     dependencies:
@@ -9172,13 +9178,17 @@ snapshots:
       '@thi.ng/arrays': 1.0.3
       '@thi.ng/checks': 2.9.11
 
-  '@tsconfig/node10@1.0.11': {}
+  '@tsconfig/node10@1.0.11':
+    optional: true
 
-  '@tsconfig/node12@1.0.11': {}
+  '@tsconfig/node12@1.0.11':
+    optional: true
 
-  '@tsconfig/node14@1.0.3': {}
+  '@tsconfig/node14@1.0.3':
+    optional: true
 
-  '@tsconfig/node16@1.0.4': {}
+  '@tsconfig/node16@1.0.4':
+    optional: true
 
   '@types/auth-header@1.0.6': {}
 
@@ -9480,7 +9490,7 @@ snapshots:
       '@typescript-eslint/types': 8.26.0
       eslint-visitor-keys: 4.2.0
 
-  '@vitest/coverage-v8@3.0.7(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0))':
+  '@vitest/coverage-v8@3.0.7(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))':
     dependencies:
       '@ampproject/remapping': 2.3.0
       '@bcoe/v8-coverage': 1.0.2
@@ -9494,17 +9504,17 @@ snapshots:
       std-env: 3.8.0
       test-exclude: 7.0.1
       tinyrainbow: 2.0.0
-      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0)
+      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
     transitivePeerDependencies:
       - supports-color
 
-  '@vitest/eslint-plugin@1.1.31(@typescript-eslint/utils@8.26.0(eslint@9.21.0)(typescript@5.8.2))(eslint@9.21.0)(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0))':
+  '@vitest/eslint-plugin@1.1.31(@typescript-eslint/utils@8.26.0(eslint@9.21.0)(typescript@5.8.2))(eslint@9.21.0)(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))':
     dependencies:
       '@typescript-eslint/utils': 8.26.0(eslint@9.21.0)(typescript@5.8.2)
       eslint: 9.21.0
     optionalDependencies:
       typescript: 5.8.2
-      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0)
+      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
 
   '@vitest/expect@3.0.7':
     dependencies:
@@ -9513,13 +9523,13 @@ snapshots:
       chai: 5.2.0
       tinyrainbow: 2.0.0
 
-  '@vitest/mocker@3.0.7(vite@6.2.0(@types/node@22.13.5)(yaml@2.7.0))':
+  '@vitest/mocker@3.0.7(vite@6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))':
     dependencies:
       '@vitest/spy': 3.0.7
       estree-walker: 3.0.3
       magic-string: 0.30.17
     optionalDependencies:
-      vite: 6.2.0(@types/node@22.13.5)(yaml@2.7.0)
+      vite: 6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
 
   '@vitest/pretty-format@3.0.7':
     dependencies:
@@ -9619,6 +9629,7 @@ snapshots:
   acorn-walk@8.3.4:
     dependencies:
       acorn: 8.14.0
+    optional: true
 
   acorn@8.14.0: {}
 
@@ -9686,7 +9697,8 @@ snapshots:
 
   archy@1.0.0: {}
 
-  arg@4.1.3: {}
+  arg@4.1.3:
+    optional: true
 
   argparse@1.0.10:
     dependencies:
@@ -10226,7 +10238,8 @@ snapshots:
       - ts-node
     optional: true
 
-  create-require@1.1.1: {}
+  create-require@1.1.1:
+    optional: true
 
   croner@9.0.0: {}
 
@@ -10347,7 +10360,8 @@ snapshots:
 
   diff-sequences@29.6.3: {}
 
-  diff@4.0.2: {}
+  diff@4.0.2:
+    optional: true
 
   diff@5.2.0: {}
 
@@ -12268,7 +12282,8 @@ snapshots:
     dependencies:
       semver: 7.7.1
 
-  make-error@1.3.6: {}
+  make-error@1.3.6:
+    optional: true
 
   make-fetch-happen@13.0.1:
     dependencies:
@@ -14130,6 +14145,7 @@ snapshots:
       typescript: 5.8.2
       v8-compile-cache-lib: 3.0.1
       yn: 3.1.1
+    optional: true
 
   tsconfck@3.1.5(typescript@5.8.2):
     optionalDependencies:
@@ -14144,6 +14160,13 @@ snapshots:
 
   tslib@2.8.1: {}
 
+  tsx@4.19.3:
+    dependencies:
+      esbuild: 0.25.0
+      get-tsconfig: 4.10.0
+    optionalDependencies:
+      fsevents: 2.3.3
+
   tunnel-agent@0.6.0:
     dependencies:
       safe-buffer: 5.2.1
@@ -14345,7 +14368,8 @@ snapshots:
 
   uuid@9.0.1: {}
 
-  v8-compile-cache-lib@3.0.1: {}
+  v8-compile-cache-lib@3.0.1:
+    optional: true
 
   v8-to-istanbul@9.3.0:
     dependencies:
@@ -14377,13 +14401,13 @@ snapshots:
       unist-util-stringify-position: 2.0.3
       vfile-message: 2.0.4
 
-  vite-node@3.0.7(@types/node@22.13.5)(yaml@2.7.0):
+  vite-node@3.0.7(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0):
     dependencies:
       cac: 6.7.14
       debug: 4.4.0
       es-module-lexer: 1.6.0
       pathe: 2.0.3
-      vite: 6.2.0(@types/node@22.13.5)(yaml@2.7.0)
+      vite: 6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
     transitivePeerDependencies:
       - '@types/node'
       - jiti
@@ -14398,18 +14422,18 @@ snapshots:
       - tsx
       - yaml
 
-  vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.5)(yaml@2.7.0)):
+  vite-tsconfig-paths@5.1.4(typescript@5.8.2)(vite@6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)):
     dependencies:
       debug: 4.4.0
       globrex: 0.1.2
       tsconfck: 3.1.5(typescript@5.8.2)
     optionalDependencies:
-      vite: 6.2.0(@types/node@22.13.5)(yaml@2.7.0)
+      vite: 6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
     transitivePeerDependencies:
       - supports-color
       - typescript
 
-  vite@6.2.0(@types/node@22.13.5)(yaml@2.7.0):
+  vite@6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0):
     dependencies:
       esbuild: 0.25.0
       postcss: 8.5.3
@@ -14417,18 +14441,19 @@ snapshots:
     optionalDependencies:
       '@types/node': 22.13.5
       fsevents: 2.3.3
+      tsx: 4.19.3
       yaml: 2.7.0
 
-  vitest-mock-extended@3.0.1(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0)):
+  vitest-mock-extended@3.0.1(typescript@5.8.2)(vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)):
     dependencies:
       ts-essentials: 10.0.4(typescript@5.8.2)
       typescript: 5.8.2
-      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0)
+      vitest: 3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
 
-  vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(yaml@2.7.0):
+  vitest@3.0.7(@types/debug@4.1.12)(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0):
     dependencies:
       '@vitest/expect': 3.0.7
-      '@vitest/mocker': 3.0.7(vite@6.2.0(@types/node@22.13.5)(yaml@2.7.0))
+      '@vitest/mocker': 3.0.7(vite@6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0))
       '@vitest/pretty-format': 3.0.7
       '@vitest/runner': 3.0.7
       '@vitest/snapshot': 3.0.7
@@ -14444,8 +14469,8 @@ snapshots:
       tinyexec: 0.3.2
       tinypool: 1.0.2
       tinyrainbow: 2.0.0
-      vite: 6.2.0(@types/node@22.13.5)(yaml@2.7.0)
-      vite-node: 3.0.7(@types/node@22.13.5)(yaml@2.7.0)
+      vite: 6.2.0(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
+      vite-node: 3.0.7(@types/node@22.13.5)(tsx@4.19.3)(yaml@2.7.0)
       why-is-node-running: 2.3.0
     optionalDependencies:
       '@types/debug': 4.1.12
@@ -14657,7 +14682,8 @@ snapshots:
       buffer-crc32: 0.2.13
       fd-slicer: 1.1.0
 
-  yn@3.1.1: {}
+  yn@3.1.1:
+    optional: true
 
   yocto-queue@0.1.0: {}
 
diff --git a/test/to-migrate.ts b/test/to-migrate.ts
index dd800e944550fdd8938f9111634462f7e1c57461..57dc34c77f7f49bfe80827667da5a27e00c10b47 100644
--- a/test/to-migrate.ts
+++ b/test/to-migrate.ts
@@ -26,7 +26,7 @@ expect.extend({
   ) {
     // async load to avoid circular dependency
     const { MigrationsService } = await import(
-      './../lib/config/migrations/migrations-service'
+      './../lib/config/migrations/migrations-service.js'
     );
     class CustomMigrationsService extends MigrationsService {
       public static override getMigrations(
diff --git a/tools/check-fenced-code.mjs b/tools/check-fenced-code.mjs
index 84e03cd0c86a3b018e7a1092b52c2b555681b153..b53b2b53c7d8779434e34888dcaa99b92b0ceebb 100644
--- a/tools/check-fenced-code.mjs
+++ b/tools/check-fenced-code.mjs
@@ -15,7 +15,7 @@ markdown.enable(['fence']);
 /**
  *
  * @param {string} file
- * @param {import('markdown-it').Token} token
+ * @param {import('markdown-it/lib/token.mjs').default} token
  */
 function checkValidJson(file, token) {
   const start = token.map ? token.map[0] + 1 : 0;
diff --git a/tools/static-data/generate-distro-info.mjs b/tools/static-data/generate-distro-info.mjs
index 21ccf3c7ecdab19ee144c748bc4d5982ef4a972e..eb063ae163a09927581c6c4e260a1124d87813b2 100644
--- a/tools/static-data/generate-distro-info.mjs
+++ b/tools/static-data/generate-distro-info.mjs
@@ -1,4 +1,3 @@
-import got from 'got';
 import { updateJsonFile } from './utils.mjs';
 
 const ubuntuUrl = 'https://debian.pages.debian.net/distro-info-data/ubuntu.csv';
@@ -52,8 +51,12 @@ function csvToJson(raw) {
  * @param {string} file File path to update
  */
 async function update(url, file) {
-  const res = await got(url);
-  const csv = res.body;
+  const res = await fetch(url);
+  if (!res.ok) {
+    console.error(`Failed to fetch ${url}`, res);
+    process.exit(1);
+  }
+  const csv = await res.text();
   const json = csvToJson(csv);
   await updateJsonFile(file, json);
 }
diff --git a/tools/static-data/generate-node-schedule.mjs b/tools/static-data/generate-node-schedule.mjs
index 1f5e09afeecef0b6e7d76277744215caedc37e24..a669ffa5e396b4bb3aae97616aaffc3430cba9cf 100644
--- a/tools/static-data/generate-node-schedule.mjs
+++ b/tools/static-data/generate-node-schedule.mjs
@@ -1,4 +1,3 @@
-import got from 'got';
 import { updateJsonFile } from './utils.mjs';
 
 const dataUrl =
@@ -6,6 +5,10 @@ const dataUrl =
 
 await (async () => {
   console.log('Generating node schedule');
-  const { body } = await got(dataUrl);
-  await updateJsonFile('./data/node-js-schedule.json', body);
+  const res = await fetch(dataUrl);
+  if (!res.ok) {
+    console.error(`Failed to fetch ${dataUrl}`, res);
+    process.exit(1);
+  }
+  await updateJsonFile('./data/node-js-schedule.json', await res.text());
 })();
diff --git a/tools/static-data/utils.mjs b/tools/static-data/utils.mjs
index d344e3fd8f9a54a0bbf8dcbb257c25b66c9a711d..a508711daac13e49359a8ced8e593eeacbd7087e 100644
--- a/tools/static-data/utils.mjs
+++ b/tools/static-data/utils.mjs
@@ -3,7 +3,7 @@ import fs from 'fs-extra';
 /**
  * Update given file with new provided data.
  * @param {string} file Path to a data file
- * @param {string} newData New data to be written
+ * @param {string|NodeJS.ArrayBufferView} newData New data to be written
  */
 export async function updateJsonFile(file, newData) {
   try {
diff --git a/tsconfig.app.json b/tsconfig.app.json
index d566f6792630354fc586db872256b5d71f26e980..58c91536785950455118ff67df2172bd92be4903 100644
--- a/tsconfig.app.json
+++ b/tsconfig.app.json
@@ -5,7 +5,6 @@
     "sourceMap": true,
     "inlineSources": true,
     "importHelpers": true,
-    "moduleResolution": "node10",
     "types": ["node"],
     "paths": {} // no paths for production
   },
diff --git a/tsconfig.json b/tsconfig.json
index a5d00ee31bf1321bb5fc5572661fbdb8f9cb34d7..04d5a09a0a0957bdd9f8176f5a4e7ed5a765d641 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -6,8 +6,8 @@
     "outDir": "./dist",
     /* https://github.com/microsoft/TypeScript/wiki/Node-Target-Mapping */
     "target": "es2022",
-    "moduleResolution": "node",
-    "module": "CommonJS",
+    "moduleResolution": "nodenext",
+    "module": "nodenext",
     "sourceMap": true,
     "allowSyntheticDefaultImports": true,
     "esModuleInterop": true,
@@ -27,7 +27,7 @@
       "~test/*": ["./test/*"]
     }
   },
-  "include": ["**/*.ts", "**/*.js", "**/*.mjs", "**/*.cjs"],
+  "include": ["**/*.mts", "**/*.ts", "**/*.js", "**/*.mjs", "**/*.cjs"],
   "exclude": [
     "node_modules",
     "./.cache",
@@ -36,9 +36,7 @@
     "coverage",
     "config.js",
     "tmp",
-    "tools/mkdocs/site",
-    /* TODO: fix me */
-    "eslint.config.mjs"
+    "tools/mkdocs/site"
   ],
   "ts-node": {
     "transpileOnly": true,
diff --git a/vitest.config.ts b/vitest.config.mts
similarity index 97%
rename from vitest.config.ts
rename to vitest.config.mts
index 237d98eca691bb86feb35473227ebf89e25ae340..530485934c2c79a32aa2ee090059568b43c2df02 100644
--- a/vitest.config.ts
+++ b/vitest.config.mts
@@ -6,11 +6,11 @@ import {
   defineConfig,
   mergeConfig,
 } from 'vitest/config';
-import { testShards } from './tools/test/shards';
+import { testShards } from './tools/test/shards.js';
 import {
   getCoverageIgnorePatterns,
   normalizePattern,
-} from './tools/test/utils';
+} from './tools/test/utils.js';
 
 const ci = !!process.env.CI;