diff --git a/lib/logger/index.spec.ts b/lib/logger/index.spec.ts
index ccf5403a29f189a08f0cf5968849d693bac987a8..c8422d469e2be6d814074a22f4c13f63db0d0385 100644
--- a/lib/logger/index.spec.ts
+++ b/lib/logger/index.spec.ts
@@ -1,4 +1,6 @@
-import _fs from 'fs-extra';
+import type { WriteStream } from 'node:fs';
+import fs from 'fs-extra';
+import { partial } from '../../test/util';
 import { add } from '../util/host-rules';
 import { addSecretForSanitizing as addSecret } from '../util/sanitize';
 import {
@@ -16,9 +18,6 @@ import {
 
 jest.unmock('.');
 
-jest.mock('fs-extra');
-const fs: any = _fs;
-
 describe('logger/index', () => {
   it('inits', () => {
     expect(logger).toBeDefined();
@@ -97,12 +96,15 @@ describe('logger/index', () => {
 
   it('supports file-based logging', () => {
     let chunk = '';
-    fs.createWriteStream.mockReturnValueOnce({
-      writable: true,
-      write(x: string) {
-        chunk = x;
-      },
-    });
+    jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(
+      partial<WriteStream>({
+        writable: true,
+        write(x: string): boolean {
+          chunk = x;
+          return true;
+        },
+      })
+    );
 
     addStream({
       name: 'logfile',
@@ -117,12 +119,15 @@ describe('logger/index', () => {
 
   it('handles cycles', () => {
     let logged: Record<string, any> = {};
-    fs.createWriteStream.mockReturnValueOnce({
-      writable: true,
-      write(x: string) {
-        logged = JSON.parse(x);
-      },
-    });
+    jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(
+      partial<WriteStream>({
+        writable: true,
+        write(x: string): boolean {
+          logged = JSON.parse(x);
+          return true;
+        },
+      })
+    );
 
     addStream({
       name: 'logfile',
@@ -142,12 +147,15 @@ describe('logger/index', () => {
 
   it('sanitizes secrets', () => {
     let logged: Record<string, any> = {};
-    fs.createWriteStream.mockReturnValueOnce({
-      writable: true,
-      write(x: string) {
-        logged = JSON.parse(x);
-      },
-    });
+    jest.spyOn(fs, 'createWriteStream').mockReturnValueOnce(
+      partial<WriteStream>({
+        writable: true,
+        write(x: string): boolean {
+          logged = JSON.parse(x);
+          return true;
+        },
+      })
+    );
 
     addStream({
       name: 'logfile',
diff --git a/lib/modules/manager/bundler/artifacts.spec.ts b/lib/modules/manager/bundler/artifacts.spec.ts
index a2eab121cdc71fae915e47813730e27572608ad5..841f8ecc899d2dd50797d3a26445a29913855d02 100644
--- a/lib/modules/manager/bundler/artifacts.spec.ts
+++ b/lib/modules/manager/bundler/artifacts.spec.ts
@@ -23,7 +23,6 @@ import { updateArtifacts } from '.';
 const datasource = mocked(_datasource);
 const bundlerHostRules = mocked(_bundlerHostRules);
 
-jest.mock('fs-extra');
 jest.mock('../../../util/exec/env');
 jest.mock('../../datasource');
 jest.mock('../../../util/fs');
diff --git a/lib/modules/manager/terraform/extract.ts b/lib/modules/manager/terraform/extract.ts
index 605f4d364b41ba4628793dee571987098d218c40..e15cc17af3d58b6fe5e422c77ab607e30668fa62 100644
--- a/lib/modules/manager/terraform/extract.ts
+++ b/lib/modules/manager/terraform/extract.ts
@@ -37,7 +37,7 @@ export async function extractPackageFile(
   );
 
   const dependencies = [];
-  const hclMap = hcl.parseHCL(content);
+  const hclMap = await hcl.parseHCL(content, fileName);
   if (is.nullOrUndefined(hclMap)) {
     logger.trace({ fileName }, 'failed to parse HCL file');
     return null;
diff --git a/lib/modules/manager/terraform/hcl/index.spec.ts b/lib/modules/manager/terraform/hcl/index.spec.ts
index bd9be23427568747c9e8543bf3171ccf9c6bd034..70cf21a38a79a6fe7249200d4204f41c537f7f04 100644
--- a/lib/modules/manager/terraform/hcl/index.spec.ts
+++ b/lib/modules/manager/terraform/hcl/index.spec.ts
@@ -8,8 +8,8 @@ const lockedVersion = Fixtures.get('lockedVersion.tf');
 
 describe('modules/manager/terraform/hcl/index', () => {
   describe('parseHCL()', () => {
-    it('should return flat modules', () => {
-      const res = parseHCL(modulesTF);
+    it('should return flat modules', async () => {
+      const res = await parseHCL(modulesTF, 'file.tf');
       expect(res?.module).toBeDefined();
       expect(Object.keys(res!.module!)).toBeArrayOfSize(6);
       expect(res).toMatchObject({
@@ -50,8 +50,8 @@ describe('modules/manager/terraform/hcl/index', () => {
       });
     });
 
-    it('should return nested terraform block', () => {
-      const res = parseHCL(lockedVersion);
+    it('should return nested terraform block', async () => {
+      const res = await parseHCL(lockedVersion, 'file.tf');
       expect(res).toMatchObject({
         terraform: [
           {
@@ -67,8 +67,8 @@ describe('modules/manager/terraform/hcl/index', () => {
       });
     });
 
-    it('should return resource blocks', () => {
-      const res = parseHCL(resourcesTF);
+    it('should return resource blocks', async () => {
+      const res = await parseHCL(resourcesTF, 'file.tf');
       expect(res).toMatchObject({
         resource: {
           docker_container: {
diff --git a/lib/modules/manager/terraform/hcl/index.ts b/lib/modules/manager/terraform/hcl/index.ts
index 61803c995dda4dde1f880038802a231d7c8ad798..8691daf3801a5f89efbed2aafba3e52c1c9a7dc0 100644
--- a/lib/modules/manager/terraform/hcl/index.ts
+++ b/lib/modules/manager/terraform/hcl/index.ts
@@ -1,10 +1,12 @@
-import * as hcl_parser from 'hcl2-parser';
-
+import { parse } from '@cdktf/hcl2json';
 import type { TerraformDefinitionFile } from './types';
 
-export function parseHCL(content: string): TerraformDefinitionFile | null {
+export async function parseHCL(
+  content: string,
+  fileName: string
+): Promise<TerraformDefinitionFile | null> {
   try {
-    return hcl_parser.parseToObject(content)[0];
+    return await parse(fileName, content);
   } catch (err) /* istanbul ignore next */ {
     return null;
   }
diff --git a/lib/util/git/private-key.spec.ts b/lib/util/git/private-key.spec.ts
index d3921992b2df424c3edfd27bbd3852252e8e1321..9ecbbe74f0b2613bd3083c99739a7cbc1c205352 100644
--- a/lib/util/git/private-key.spec.ts
+++ b/lib/util/git/private-key.spec.ts
@@ -1,9 +1,10 @@
+import { Fixtures } from '../../../test/fixtures';
 import { mocked } from '../../../test/util';
 import * as exec_ from '../exec';
 import { configSigningKey, writePrivateKey } from './private-key';
 import { setPrivateKey } from '.';
 
-jest.mock('fs-extra');
+jest.mock('fs-extra', () => Fixtures.fsExtra());
 jest.mock('../exec');
 
 const exec = mocked(exec_);
diff --git a/lib/workers/global/index.spec.ts b/lib/workers/global/index.spec.ts
index daae4d825c3036fa153df42b9c4cacbf1eb3d467..8c371725b1569a8443831dd4bf5017f60a086afb 100644
--- a/lib/workers/global/index.spec.ts
+++ b/lib/workers/global/index.spec.ts
@@ -1,11 +1,11 @@
 import { expect } from '@jest/globals';
 import { ERROR, WARN } from 'bunyan';
-import * as _fs from 'fs-extra';
+import fs from 'fs-extra';
 import { logger, mocked } from '../../../test/util';
 import * as _presets from '../../config/presets';
 import { CONFIG_PRESETS_INVALID } from '../../constants/error-messages';
 import { DockerDatasource } from '../../modules/datasource/docker';
-import * as _platform from '../../modules/platform';
+import * as platform from '../../modules/platform';
 import * as secrets from '../../util/sanitize';
 import * as repositoryWorker from '../repository';
 import * as configParser from './config/parse';
@@ -16,20 +16,33 @@ jest.mock('../repository');
 jest.mock('../../util/fs');
 jest.mock('../../config/presets');
 
-jest.mock('fs-extra');
-const fs = mocked(_fs);
-const platform = mocked(_platform);
+jest.mock('fs-extra', () => {
+  const realFs = jest.requireActual<typeof fs>('fs-extra');
+  return {
+    ensureDir: jest.fn(),
+    remove: jest.fn(),
+    readFile: jest.fn((file: string, options: any) => {
+      if (file.endsWith('.wasm.gz')) {
+        return realFs.readFile(file, options);
+      }
+      return undefined;
+    }),
+    writeFile: jest.fn(),
+    outputFile: jest.fn(),
+  };
+});
 
 // imports are readonly
 const presets = mocked(_presets);
 
 const addSecretForSanitizing = jest.spyOn(secrets, 'addSecretForSanitizing');
 const parseConfigs = jest.spyOn(configParser, 'parseConfigs');
+const initPlatform = jest.spyOn(platform, 'initPlatform');
 
 describe('workers/global/index', () => {
   beforeEach(() => {
     logger.getProblems.mockImplementationOnce(() => []);
-    platform.initPlatform.mockImplementation((input) => Promise.resolve(input));
+    initPlatform.mockImplementation((input) => Promise.resolve(input));
     delete process.env.AWS_SECRET_ACCESS_KEY;
     delete process.env.AWS_SESSION_TOKEN;
   });
diff --git a/package.json b/package.json
index 091bdf493795051f7ab6e64295dc60757a3cd35f..f840ca80aff0c7d7a71b0c5d68103d5e748a568f 100644
--- a/package.json
+++ b/package.json
@@ -142,6 +142,7 @@
     "@aws-sdk/client-rds": "3.314.0",
     "@aws-sdk/client-s3": "3.312.0",
     "@breejs/later": "4.1.0",
+    "@cdktf/hcl2json": "0.16.1",
     "@cheap-glitch/mi-cron": "1.0.1",
     "@iarna/toml": "3.0.0",
     "@opentelemetry/api": "1.4.1",
@@ -198,7 +199,6 @@
     "graph-data-structure": "3.3.0",
     "handlebars": "4.7.7",
     "hasha": "5.2.2",
-    "hcl2-parser": "1.0.3",
     "ignore": "5.2.4",
     "ini": "4.1.0",
     "js-yaml": "4.1.0",
diff --git a/test/fixtures.ts b/test/fixtures.ts
index 3b3198122074ca3a2b3304e87ad66c117ce2c188..35afd650a566ee2a271f7b5c9190d8e1e3f3c5e9 100644
--- a/test/fixtures.ts
+++ b/test/fixtures.ts
@@ -1,8 +1,10 @@
-import type fs from 'node:fs';
+import fs from 'node:fs';
 import type { PathLike, Stats } from 'node:fs';
 import { jest } from '@jest/globals';
 import callsite from 'callsite';
 import { DirectoryJSON, fs as memfs, vol } from 'memfs';
+import type { TDataOut } from 'memfs/lib/encoding';
+import type { IOptions } from 'memfs/lib/volume';
 import upath from 'upath';
 
 // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
@@ -93,7 +95,7 @@ export class Fixtures {
       ...memfs,
       pathExists: jest.fn(pathExists),
       remove: jest.fn(memfs.promises.rm),
-      readFile: jest.fn(memfs.promises.readFile),
+      readFile: jest.fn(readFile),
       writeFile: jest.fn(memfs.promises.writeFile),
       outputFile: jest.fn(outputFile),
       stat: jest.fn(stat),
@@ -107,6 +109,17 @@ export class Fixtures {
   }
 }
 
+export function readFile(
+  fileName: string,
+  options: IOptions
+): Promise<TDataOut> {
+  if (fileName.endsWith('.wasm') || fileName.endsWith('.wasm.gz')) {
+    return fs.promises.readFile(fileName, options as any);
+  }
+
+  return memfs.promises.readFile(fileName, options);
+}
+
 export async function outputFile(
   file: string,
   data: string | Buffer | Uint8Array
diff --git a/yarn.lock b/yarn.lock
index ad417556653ecae5000519df7a5e991904cc9e78..2b353c2f2ca49e20463e49c3fa876308d8b3aa5c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1525,6 +1525,13 @@
   resolved "https://registry.yarnpkg.com/@breejs/later/-/later-4.1.0.tgz#9246907f46cc9e9c9af37d791ab468d98921bcc1"
   integrity sha512-QgGnZ9b7o4k0Ai1ZbTJWwZpZcFK9d+Gb+DyNt4UT9x6IEIs5HVu0iIlmgzGqN+t9MoJSpSPo9S/Mm51UtHr3JA==
 
+"@cdktf/hcl2json@0.16.1":
+  version "0.16.1"
+  resolved "https://registry.yarnpkg.com/@cdktf/hcl2json/-/hcl2json-0.16.1.tgz#c5d4c965e150d29579d6b53a9fe6c946fca7e614"
+  integrity sha512-c0AcAzpUYREmkonxai3Jjd7P0c0/TMB+ga3Ir/02MDSdyV8VQ+1CLK2nVMJJqJTsGZkxrZGVj/jlwA6coDYaqg==
+  dependencies:
+    fs-extra "^11.1.1"
+
 "@cheap-glitch/mi-cron@1.0.1":
   version "1.0.1"
   resolved "https://registry.yarnpkg.com/@cheap-glitch/mi-cron/-/mi-cron-1.0.1.tgz#111f4ce746c269aedf74533ac806881763a68f99"
@@ -5809,11 +5816,6 @@ hasha@5.2.2:
     is-stream "^2.0.0"
     type-fest "^0.8.0"
 
-hcl2-parser@1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/hcl2-parser/-/hcl2-parser-1.0.3.tgz#096d0ff5a3c46707ace54fcb7571317f5828ff0e"
-  integrity sha512-NQUm/BFF+2nrBfeqDhhsy4DxxiLHgkeE3FywtjFiXnjSUaio3w4Tz1MQ3vGJBUhyArzOXJ24pO7JwE5LAn7Ncg==
-
 he@1.2.0:
   version "1.2.0"
   resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"