From f42d0583d56f9094a79172149d5c3e9520af5a2c Mon Sep 17 00:00:00 2001
From: Sebastian Poxhofer <secustor@users.noreply.github.com>
Date: Fri, 1 Dec 2023 12:58:43 +0100
Subject: [PATCH] refactor(utils/yaml): centralize YAML parsing to allow
 central templating handling (#26068)

---
 lib/modules/datasource/conan/index.ts         |   2 +-
 lib/modules/datasource/custom/formats/yaml.ts |   6 +-
 lib/modules/datasource/helm/common.spec.ts    |   2 +-
 lib/modules/datasource/helm/index.ts          |   2 +-
 lib/modules/manager/argocd/extract.ts         |   2 +-
 .../manager/azure-pipelines/extract.ts        |   2 +-
 lib/modules/manager/batect/extract.ts         |   2 +-
 lib/modules/manager/cloudbuild/extract.ts     |   2 +-
 lib/modules/manager/crossplane/extract.ts     |   2 +-
 lib/modules/manager/docker-compose/extract.ts |   2 +-
 lib/modules/manager/fleet/extract.ts          |   2 +-
 lib/modules/manager/flux/extract.ts           |   2 +-
 lib/modules/manager/github-actions/extract.ts |   2 +-
 .../manager/gitlabci-include/common.spec.ts   |   2 +-
 .../manager/gitlabci-include/extract.ts       |   2 +-
 lib/modules/manager/gitlabci/extract.ts       |   2 +-
 .../manager/helm-requirements/extract.ts      |   2 +-
 lib/modules/manager/helm-values/extract.ts    |   2 +-
 lib/modules/manager/helmfile/extract.ts       |  14 +--
 lib/modules/manager/helmsman/extract.ts       |   2 +-
 lib/modules/manager/helmv3/artifacts.ts       |   2 +-
 lib/modules/manager/helmv3/extract.ts         |   2 +-
 lib/modules/manager/helmv3/update.spec.ts     |   2 +-
 lib/modules/manager/jenkins/extract.ts        |   2 +-
 lib/modules/manager/kubernetes/extract.ts     |   2 +-
 lib/modules/manager/kustomize/extract.ts      |   2 +-
 lib/modules/manager/npm/extract/pnpm.spec.ts  |   2 +-
 lib/modules/manager/npm/extract/pnpm.ts       |   2 +-
 lib/modules/manager/npm/post-update/index.ts  |   2 +-
 lib/modules/manager/npm/post-update/pnpm.ts   |   2 +-
 lib/modules/manager/pre-commit/extract.ts     |   2 +-
 lib/modules/manager/tekton/extract.ts         |   2 +-
 lib/modules/manager/travis/extract.ts         |   2 +-
 lib/modules/manager/velaci/extract.ts         |   2 +-
 lib/modules/manager/woodpecker/extract.ts     |   2 +-
 lib/util/schema-utils.ts                      |   2 +-
 lib/util/yaml.spec.ts                         | 115 ++++++++++++++++++
 lib/util/yaml.ts                              |  41 +++++++
 lib/workers/global/config/parse/file.ts       |   2 +-
 39 files changed, 199 insertions(+), 47 deletions(-)
 create mode 100644 lib/util/yaml.spec.ts
 create mode 100644 lib/util/yaml.ts

diff --git a/lib/modules/datasource/conan/index.ts b/lib/modules/datasource/conan/index.ts
index b8cc3d77d3..b4fae4da51 100644
--- a/lib/modules/datasource/conan/index.ts
+++ b/lib/modules/datasource/conan/index.ts
@@ -1,9 +1,9 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { cache } from '../../../util/cache/package/decorator';
 import { GithubHttp } from '../../../util/http/github';
 import { ensureTrailingSlash, joinUrlParts } from '../../../util/url';
+import { load } from '../../../util/yaml';
 import * as allVersioning from '../../versioning';
 import { Datasource } from '../datasource';
 import type {
diff --git a/lib/modules/datasource/custom/formats/yaml.ts b/lib/modules/datasource/custom/formats/yaml.ts
index 076ded0a7f..f20f32d130 100644
--- a/lib/modules/datasource/custom/formats/yaml.ts
+++ b/lib/modules/datasource/custom/formats/yaml.ts
@@ -1,18 +1,18 @@
-import yaml from 'js-yaml';
 import { readLocalFile } from '../../../../util/fs';
 import type { Http } from '../../../../util/http';
+import { load } from '../../../../util/yaml';
 import type { CustomDatasourceFetcher } from './types';
 
 export class YamlFetcher implements CustomDatasourceFetcher {
   async fetch(http: Http, registryURL: string): Promise<unknown> {
     const response = await http.get(registryURL);
 
-    return yaml.load(response.body);
+    return load(response.body);
   }
 
   async readFile(registryURL: string): Promise<unknown> {
     const fileContent = await readLocalFile(registryURL, 'utf8');
 
-    return yaml.load(fileContent!);
+    return load(fileContent!);
   }
 }
diff --git a/lib/modules/datasource/helm/common.spec.ts b/lib/modules/datasource/helm/common.spec.ts
index 2de9e3d13b..63db19e8c4 100644
--- a/lib/modules/datasource/helm/common.spec.ts
+++ b/lib/modules/datasource/helm/common.spec.ts
@@ -1,5 +1,5 @@
-import { load } from 'js-yaml';
 import { Fixtures } from '../../../../test/fixtures';
+import { load } from '../../../util/yaml';
 import { findSourceUrl } from './common';
 import type { HelmRepository } from './types';
 
diff --git a/lib/modules/datasource/helm/index.ts b/lib/modules/datasource/helm/index.ts
index 5f5eb92fde..aeff449f25 100644
--- a/lib/modules/datasource/helm/index.ts
+++ b/lib/modules/datasource/helm/index.ts
@@ -1,9 +1,9 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { cache } from '../../../util/cache/package/decorator';
 import type { HttpResponse } from '../../../util/http/types';
 import { ensureTrailingSlash } from '../../../util/url';
+import { load } from '../../../util/yaml';
 import * as helmVersioning from '../../versioning/helm';
 import { Datasource } from '../datasource';
 import type { GetReleasesConfig, ReleaseResult } from '../types';
diff --git a/lib/modules/manager/argocd/extract.ts b/lib/modules/manager/argocd/extract.ts
index 249fb4a9a9..8553a748f0 100644
--- a/lib/modules/manager/argocd/extract.ts
+++ b/lib/modules/manager/argocd/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { coerceArray } from '../../../util/array';
 import { trimTrailingSlash } from '../../../util/url';
+import { loadAll } from '../../../util/yaml';
 import { DockerDatasource } from '../../datasource/docker';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { HelmDatasource } from '../../datasource/helm';
diff --git a/lib/modules/manager/azure-pipelines/extract.ts b/lib/modules/manager/azure-pipelines/extract.ts
index 33f770c2f9..b5d353dec5 100644
--- a/lib/modules/manager/azure-pipelines/extract.ts
+++ b/lib/modules/manager/azure-pipelines/extract.ts
@@ -1,9 +1,9 @@
-import { load } from 'js-yaml';
 import { GlobalConfig } from '../../../config/global';
 import { logger } from '../../../logger';
 import { coerceArray } from '../../../util/array';
 import { regEx } from '../../../util/regex';
 import { joinUrlParts } from '../../../util/url';
+import { load } from '../../../util/yaml';
 import { AzurePipelinesTasksDatasource } from '../../datasource/azure-pipelines-tasks';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { getDep } from '../dockerfile/extract';
diff --git a/lib/modules/manager/batect/extract.ts b/lib/modules/manager/batect/extract.ts
index b2c8105dc2..fd9f4c4a03 100644
--- a/lib/modules/manager/batect/extract.ts
+++ b/lib/modules/manager/batect/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import upath from 'upath';
 import { logger } from '../../../logger';
 import { readLocalFile } from '../../../util/fs';
+import { load } from '../../../util/yaml';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { id as dockerVersioning } from '../../versioning/docker';
 import { id as semverVersioning } from '../../versioning/semver';
diff --git a/lib/modules/manager/cloudbuild/extract.ts b/lib/modules/manager/cloudbuild/extract.ts
index b00c00e812..6f04657b95 100644
--- a/lib/modules/manager/cloudbuild/extract.ts
+++ b/lib/modules/manager/cloudbuild/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
+import { load } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFileContent } from '../types';
 
diff --git a/lib/modules/manager/crossplane/extract.ts b/lib/modules/manager/crossplane/extract.ts
index 35f70e8783..605a70fdf5 100644
--- a/lib/modules/manager/crossplane/extract.ts
+++ b/lib/modules/manager/crossplane/extract.ts
@@ -1,5 +1,5 @@
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
+import { loadAll } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type {
   ExtractConfig,
diff --git a/lib/modules/manager/docker-compose/extract.ts b/lib/modules/manager/docker-compose/extract.ts
index 67ffe6db23..06ccb81474 100644
--- a/lib/modules/manager/docker-compose/extract.ts
+++ b/lib/modules/manager/docker-compose/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { newlineRegex, regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { ExtractConfig, PackageFileContent } from '../types';
 import type { DockerComposeConfig } from './types';
diff --git a/lib/modules/manager/fleet/extract.ts b/lib/modules/manager/fleet/extract.ts
index 65fa42d8de..cee52e074f 100644
--- a/lib/modules/manager/fleet/extract.ts
+++ b/lib/modules/manager/fleet/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { regEx } from '../../../util/regex';
+import { loadAll } from '../../../util/yaml';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { HelmDatasource } from '../../datasource/helm';
 import { checkIfStringIsPath } from '../terraform/util';
diff --git a/lib/modules/manager/flux/extract.ts b/lib/modules/manager/flux/extract.ts
index 89c260319a..036c1a46f1 100644
--- a/lib/modules/manager/flux/extract.ts
+++ b/lib/modules/manager/flux/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { readLocalFile } from '../../../util/fs';
 import { regEx } from '../../../util/regex';
+import { loadAll } from '../../../util/yaml';
 import { BitbucketTagsDatasource } from '../../datasource/bitbucket-tags';
 import { DockerDatasource } from '../../datasource/docker';
 import { GitRefsDatasource } from '../../datasource/git-refs';
diff --git a/lib/modules/manager/github-actions/extract.ts b/lib/modules/manager/github-actions/extract.ts
index 4f0e59b5ae..e0415683c9 100644
--- a/lib/modules/manager/github-actions/extract.ts
+++ b/lib/modules/manager/github-actions/extract.ts
@@ -1,9 +1,9 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { GlobalConfig } from '../../../config/global';
 import { logger } from '../../../logger';
 import { isNotNullOrUndefined } from '../../../util/array';
 import { newlineRegex, regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { GithubRunnersDatasource } from '../../datasource/github-runners';
 import { GithubTagsDatasource } from '../../datasource/github-tags';
 import * as dockerVersioning from '../../versioning/docker';
diff --git a/lib/modules/manager/gitlabci-include/common.spec.ts b/lib/modules/manager/gitlabci-include/common.spec.ts
index 7484698925..c71f0cf03f 100644
--- a/lib/modules/manager/gitlabci-include/common.spec.ts
+++ b/lib/modules/manager/gitlabci-include/common.spec.ts
@@ -1,5 +1,5 @@
-import { load } from 'js-yaml';
 import { Fixtures } from '../../../../test/fixtures';
+import { load } from '../../../util/yaml';
 import type { GitlabPipeline } from '../gitlabci/types';
 import { replaceReferenceTags } from '../gitlabci/utils';
 import {
diff --git a/lib/modules/manager/gitlabci-include/extract.ts b/lib/modules/manager/gitlabci-include/extract.ts
index 9c4a289204..b38d730e48 100644
--- a/lib/modules/manager/gitlabci-include/extract.ts
+++ b/lib/modules/manager/gitlabci-include/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { GlobalConfig } from '../../../config/global';
 import { logger } from '../../../logger';
 import { regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { GitlabTagsDatasource } from '../../datasource/gitlab-tags';
 import type {
   GitlabInclude,
diff --git a/lib/modules/manager/gitlabci/extract.ts b/lib/modules/manager/gitlabci/extract.ts
index 00da59a140..a520e0349a 100644
--- a/lib/modules/manager/gitlabci/extract.ts
+++ b/lib/modules/manager/gitlabci/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { readLocalFile } from '../../../util/fs';
 import { trimLeadingSlash } from '../../../util/url';
+import { load } from '../../../util/yaml';
 import type {
   ExtractConfig,
   PackageDependency,
diff --git a/lib/modules/manager/helm-requirements/extract.ts b/lib/modules/manager/helm-requirements/extract.ts
index 5a6a8567aa..52834af825 100644
--- a/lib/modules/manager/helm-requirements/extract.ts
+++ b/lib/modules/manager/helm-requirements/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
+import { load } from '../../../util/yaml';
 import { HelmDatasource } from '../../datasource/helm';
 import type {
   ExtractConfig,
diff --git a/lib/modules/manager/helm-values/extract.ts b/lib/modules/manager/helm-values/extract.ts
index 1f9ea32f61..e75026dec8 100644
--- a/lib/modules/manager/helm-values/extract.ts
+++ b/lib/modules/manager/helm-values/extract.ts
@@ -1,5 +1,5 @@
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
+import { loadAll } from '../../../util/yaml';
 import { id as dockerVersioning } from '../../versioning/docker';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFileContent } from '../types';
diff --git a/lib/modules/manager/helmfile/extract.ts b/lib/modules/manager/helmfile/extract.ts
index 0a29612900..0ca299a223 100644
--- a/lib/modules/manager/helmfile/extract.ts
+++ b/lib/modules/manager/helmfile/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { regEx } from '../../../util/regex';
+import { loadAll } from '../../../util/yaml';
 import { DockerDatasource } from '../../datasource/docker';
 import { HelmDatasource } from '../../datasource/helm';
 import type {
@@ -18,13 +18,6 @@ import {
 const isValidChartName = (name: string | undefined): boolean =>
   !!name && !regEx(/[!@#$%^&*(),.?":{}/|<>A-Z]/).test(name);
 
-function extractYaml(content: string): string {
-  // regex remove go templated ({{ . }}) values
-  return content
-    .replace(regEx(/{{`.+?`}}/gs), '')
-    .replace(regEx(/{{.+?}}/g), '');
-}
-
 function isLocalPath(possiblePath: string): boolean {
   return ['./', '../', '/'].some((localPrefix) =>
     possiblePath.startsWith(localPrefix),
@@ -42,7 +35,10 @@ export async function extractPackageFile(
   // Record kustomization usage for all deps, since updating artifacts is run on the helmfile.yaml as a whole.
   let needKustomize = false;
   try {
-    docs = loadAll(extractYaml(content), null, { json: true }) as Doc[];
+    docs = loadAll(content, null, {
+      removeTemplates: true,
+      json: true,
+    }) as Doc[];
   } catch (err) {
     logger.debug(
       { err, packageFile },
diff --git a/lib/modules/manager/helmsman/extract.ts b/lib/modules/manager/helmsman/extract.ts
index 504b2f43af..f776cd0b2f 100644
--- a/lib/modules/manager/helmsman/extract.ts
+++ b/lib/modules/manager/helmsman/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { DockerDatasource } from '../../datasource/docker';
 import { HelmDatasource } from '../../datasource/helm';
 import type {
diff --git a/lib/modules/manager/helmv3/artifacts.ts b/lib/modules/manager/helmv3/artifacts.ts
index 93616df86e..7f7df8d6c6 100644
--- a/lib/modules/manager/helmv3/artifacts.ts
+++ b/lib/modules/manager/helmv3/artifacts.ts
@@ -1,5 +1,4 @@
 import is from '@sindresorhus/is';
-import yaml from 'js-yaml';
 import pMap from 'p-map';
 import { quote } from 'shlex';
 import { TEMPORARY_ERROR } from '../../../constants/error-messages';
@@ -14,6 +13,7 @@ import {
 } from '../../../util/fs';
 import { getRepoStatus } from '../../../util/git';
 import * as hostRules from '../../../util/host-rules';
+import * as yaml from '../../../util/yaml';
 import { DockerDatasource } from '../../datasource/docker';
 import { HelmDatasource } from '../../datasource/helm';
 import type { UpdateArtifact, UpdateArtifactsResult } from '../types';
diff --git a/lib/modules/manager/helmv3/extract.ts b/lib/modules/manager/helmv3/extract.ts
index d7fc59518c..f6d9e9d464 100644
--- a/lib/modules/manager/helmv3/extract.ts
+++ b/lib/modules/manager/helmv3/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { getSiblingFileName, localPathExists } from '../../../util/fs';
+import { load } from '../../../util/yaml';
 import { HelmDatasource } from '../../datasource/helm';
 import type {
   ExtractConfig,
diff --git a/lib/modules/manager/helmv3/update.spec.ts b/lib/modules/manager/helmv3/update.spec.ts
index 43062508b6..854dcfc2e8 100644
--- a/lib/modules/manager/helmv3/update.spec.ts
+++ b/lib/modules/manager/helmv3/update.spec.ts
@@ -1,4 +1,4 @@
-import { dump } from 'js-yaml';
+import { dump } from '../../../util/yaml';
 import * as helmv3Updater from '.';
 
 describe('modules/manager/helmv3/update', () => {
diff --git a/lib/modules/manager/jenkins/extract.ts b/lib/modules/manager/jenkins/extract.ts
index c8b1dad120..51d3fa54ac 100644
--- a/lib/modules/manager/jenkins/extract.ts
+++ b/lib/modules/manager/jenkins/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { isSkipComment } from '../../../util/ignore';
 import { newlineRegex, regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { JenkinsPluginsDatasource } from '../../datasource/jenkins-plugins';
 import * as mavenVersioning from '../../versioning/maven';
 import type { PackageDependency, PackageFileContent } from '../types';
diff --git a/lib/modules/manager/kubernetes/extract.ts b/lib/modules/manager/kubernetes/extract.ts
index 4588f63d1f..9920f16007 100644
--- a/lib/modules/manager/kubernetes/extract.ts
+++ b/lib/modules/manager/kubernetes/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { newlineRegex, regEx } from '../../../util/regex';
+import { loadAll } from '../../../util/yaml';
 import {
   KubernetesApiDatasource,
   supportedApis,
diff --git a/lib/modules/manager/kustomize/extract.ts b/lib/modules/manager/kustomize/extract.ts
index 8af061713a..bca4d553c0 100644
--- a/lib/modules/manager/kustomize/extract.ts
+++ b/lib/modules/manager/kustomize/extract.ts
@@ -1,8 +1,8 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { coerceArray } from '../../../util/array';
 import { regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { DockerDatasource } from '../../datasource/docker';
 import { GitTagsDatasource } from '../../datasource/git-tags';
 import { GithubTagsDatasource } from '../../datasource/github-tags';
diff --git a/lib/modules/manager/npm/extract/pnpm.spec.ts b/lib/modules/manager/npm/extract/pnpm.spec.ts
index 9927655a77..590a941f9f 100644
--- a/lib/modules/manager/npm/extract/pnpm.spec.ts
+++ b/lib/modules/manager/npm/extract/pnpm.spec.ts
@@ -1,8 +1,8 @@
-import yaml from 'js-yaml';
 import { Fixtures } from '../../../../../test/fixtures';
 import { getFixturePath, logger, partial } from '../../../../../test/util';
 import { GlobalConfig } from '../../../../config/global';
 import * as fs from '../../../../util/fs';
+import * as yaml from '../../../../util/yaml';
 import type { PackageFile } from '../../types';
 import type { NpmManagerData } from '../types';
 import {
diff --git a/lib/modules/manager/npm/extract/pnpm.ts b/lib/modules/manager/npm/extract/pnpm.ts
index 3de992b460..d358212cdb 100644
--- a/lib/modules/manager/npm/extract/pnpm.ts
+++ b/lib/modules/manager/npm/extract/pnpm.ts
@@ -1,6 +1,5 @@
 import is from '@sindresorhus/is';
 import { findPackages } from 'find-packages';
-import { load } from 'js-yaml';
 import upath from 'upath';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
@@ -10,6 +9,7 @@ import {
   localPathExists,
   readLocalFile,
 } from '../../../../util/fs';
+import { load } from '../../../../util/yaml';
 import type { PackageFile } from '../../types';
 import type { PnpmDependencySchema, PnpmLockFile } from '../post-update/types';
 import type { NpmManagerData } from '../types';
diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts
index 5b8d92b4e1..fa318343b0 100644
--- a/lib/modules/manager/npm/post-update/index.ts
+++ b/lib/modules/manager/npm/post-update/index.ts
@@ -1,7 +1,6 @@
 // TODO: types (#22198)
 import is from '@sindresorhus/is';
 import deepmerge from 'deepmerge';
-import { dump, load } from 'js-yaml';
 import upath from 'upath';
 import { logger } from '../../../../logger';
 import { ExternalHostError } from '../../../../types/errors/external-host-error';
@@ -18,6 +17,7 @@ import type { FileChange } from '../../../../util/git/types';
 import * as hostRules from '../../../../util/host-rules';
 import { newlineRegex, regEx } from '../../../../util/regex';
 import { ensureTrailingSlash } from '../../../../util/url';
+import { dump, load } from '../../../../util/yaml';
 import { NpmDatasource } from '../../../datasource/npm';
 import { scm } from '../../../platform/scm';
 import type { PackageFile, PostUpdateConfig, Upgrade } from '../../types';
diff --git a/lib/modules/manager/npm/post-update/pnpm.ts b/lib/modules/manager/npm/post-update/pnpm.ts
index c3db89f25a..9cbffebfde 100644
--- a/lib/modules/manager/npm/post-update/pnpm.ts
+++ b/lib/modules/manager/npm/post-update/pnpm.ts
@@ -1,5 +1,4 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import upath from 'upath';
 import { GlobalConfig } from '../../../../config/global';
 import { TEMPORARY_ERROR } from '../../../../constants/error-messages';
@@ -11,6 +10,7 @@ import type {
   ToolConstraint,
 } from '../../../../util/exec/types';
 import { deleteLocalFile, readLocalFile } from '../../../../util/fs';
+import { load } from '../../../../util/yaml';
 import type { PostUpdateConfig, Upgrade } from '../../types';
 import { getNodeToolConstraint } from './node-version';
 import type { GenerateLockFileResult, PnpmLockFile } from './types';
diff --git a/lib/modules/manager/pre-commit/extract.ts b/lib/modules/manager/pre-commit/extract.ts
index 30a6eac49c..345e3d66d9 100644
--- a/lib/modules/manager/pre-commit/extract.ts
+++ b/lib/modules/manager/pre-commit/extract.ts
@@ -1,10 +1,10 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import type { SkipReason } from '../../../types';
 import { detectPlatform } from '../../../util/common';
 import { find } from '../../../util/host-rules';
 import { regEx } from '../../../util/regex';
+import { load } from '../../../util/yaml';
 import { GithubTagsDatasource } from '../../datasource/github-tags';
 import { GitlabTagsDatasource } from '../../datasource/gitlab-tags';
 import type { PackageDependency, PackageFileContent } from '../types';
diff --git a/lib/modules/manager/tekton/extract.ts b/lib/modules/manager/tekton/extract.ts
index 810a4a8673..9c4ab35d74 100644
--- a/lib/modules/manager/tekton/extract.ts
+++ b/lib/modules/manager/tekton/extract.ts
@@ -1,7 +1,7 @@
 import is from '@sindresorhus/is';
-import { loadAll } from 'js-yaml';
 import { logger } from '../../../logger';
 import { coerceArray } from '../../../util/array';
+import { loadAll } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFileContent } from '../types';
 import type {
diff --git a/lib/modules/manager/travis/extract.ts b/lib/modules/manager/travis/extract.ts
index 886cc4a0f4..9427813c07 100644
--- a/lib/modules/manager/travis/extract.ts
+++ b/lib/modules/manager/travis/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
+import { load } from '../../../util/yaml';
 import { GithubTagsDatasource } from '../../datasource/github-tags';
 import type { PackageDependency, PackageFileContent } from '../types';
 import type { TravisMatrixItem, TravisYaml } from './types';
diff --git a/lib/modules/manager/velaci/extract.ts b/lib/modules/manager/velaci/extract.ts
index 50ea39a6ea..6be65899f0 100644
--- a/lib/modules/manager/velaci/extract.ts
+++ b/lib/modules/manager/velaci/extract.ts
@@ -1,6 +1,6 @@
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
 import { coerceArray } from '../../../util/array';
+import { load } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { PackageDependency, PackageFileContent } from '../types';
 import type { VelaPipelineConfiguration } from './types';
diff --git a/lib/modules/manager/woodpecker/extract.ts b/lib/modules/manager/woodpecker/extract.ts
index 6012709aaf..a5fabef1c7 100644
--- a/lib/modules/manager/woodpecker/extract.ts
+++ b/lib/modules/manager/woodpecker/extract.ts
@@ -1,6 +1,6 @@
 import is from '@sindresorhus/is';
-import { load } from 'js-yaml';
 import { logger } from '../../../logger';
+import { load } from '../../../util/yaml';
 import { getDep } from '../dockerfile/extract';
 import type { ExtractConfig, PackageFileContent } from '../types';
 import type { WoodpeckerConfig } from './types';
diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts
index f2b5bed1e4..28698d2c8f 100644
--- a/lib/util/schema-utils.ts
+++ b/lib/util/schema-utils.ts
@@ -1,9 +1,9 @@
-import { load, loadAll } from 'js-yaml';
 import JSON5 from 'json5';
 import { DateTime } from 'luxon';
 import type { JsonArray, JsonValue } from 'type-fest';
 import { z } from 'zod';
 import { parse as parseToml } from './toml';
+import { load, loadAll } from './yaml';
 
 interface ErrorContext<T> {
   error: z.ZodError;
diff --git a/lib/util/yaml.spec.ts b/lib/util/yaml.spec.ts
new file mode 100644
index 0000000000..39b1ba55a0
--- /dev/null
+++ b/lib/util/yaml.spec.ts
@@ -0,0 +1,115 @@
+import { codeBlock } from 'common-tags';
+import { load, loadAll } from './yaml';
+
+describe('util/yaml', () => {
+  describe('loadAll', () => {
+    it('should return empty array for empty string', () => {
+      expect(loadAll(``)).toEqual([]);
+    });
+
+    it('should parse content with single document', () => {
+      expect(
+        loadAll(codeBlock`
+      myObject:
+        aString: value
+      `),
+      ).toEqual([
+        {
+          myObject: {
+            aString: 'value',
+          },
+        },
+      ]);
+    });
+
+    it('should parse content with multiple documents', () => {
+      expect(
+        loadAll(codeBlock`
+      myObject:
+        aString: value
+      ---
+      foo: bar
+      `),
+      ).toEqual([
+        {
+          myObject: {
+            aString: 'value',
+          },
+        },
+        {
+          foo: 'bar',
+        },
+      ]);
+    });
+
+    it('should parse content with templates', () => {
+      expect(
+        loadAll(
+          codeBlock`
+      myObject:
+        aString: {{ value }}
+      ---
+      foo: {{ foo.bar }}
+      `,
+          undefined,
+          { removeTemplates: true },
+        ),
+      ).toEqual([
+        {
+          myObject: {
+            aString: null,
+          },
+        },
+        {
+          foo: null,
+        },
+      ]);
+    });
+  });
+
+  describe('load', () => {
+    it('should return undefined', () => {
+      expect(load(``)).toBeUndefined();
+    });
+
+    it('should parse content with single document', () => {
+      expect(
+        load(codeBlock`
+      myObject:
+        aString: value
+      `),
+      ).toEqual({
+        myObject: {
+          aString: 'value',
+        },
+      });
+    });
+
+    it('should parse content with multiple documents', () => {
+      expect(() =>
+        load(codeBlock`
+      myObject:
+        aString: value
+      ---
+      foo: bar
+      `),
+      ).toThrow();
+    });
+
+    it('should parse content with template', () => {
+      expect(
+        load(
+          codeBlock`
+      myObject:
+        aString: {{value}}
+      `,
+          { removeTemplates: true },
+        ),
+      ).toEqual({
+        myObject: {
+          aString: null,
+        },
+      });
+    });
+  });
+});
diff --git a/lib/util/yaml.ts b/lib/util/yaml.ts
new file mode 100644
index 0000000000..620c330dfa
--- /dev/null
+++ b/lib/util/yaml.ts
@@ -0,0 +1,41 @@
+import {
+  DumpOptions,
+  LoadOptions,
+  loadAll as multiple,
+  load as single,
+  dump as upstreamDump,
+} from 'js-yaml';
+import { regEx } from './regex';
+
+interface YamlOptions extends LoadOptions {
+  removeTemplates?: boolean;
+}
+
+export function loadAll(
+  content: string,
+  iterator?: null | undefined,
+  options?: YamlOptions,
+): unknown[] {
+  const massagedContent = massageContent(content, options);
+
+  return multiple(massagedContent, iterator, options);
+}
+
+export function load(content: string, options?: YamlOptions): unknown {
+  const massagedContent = massageContent(content, options);
+  return single(massagedContent, options);
+}
+
+export function dump(obj: any, opts?: DumpOptions | undefined): string {
+  return upstreamDump(obj, opts);
+}
+
+function massageContent(content: string, options?: YamlOptions): string {
+  if (options?.removeTemplates) {
+    return content
+      .replace(regEx(/{{`.+?`}}/gs), '')
+      .replace(regEx(/{{.+?}}/g), '');
+  }
+
+  return content;
+}
diff --git a/lib/workers/global/config/parse/file.ts b/lib/workers/global/config/parse/file.ts
index 3d0ecb7d9d..f66cee56d1 100644
--- a/lib/workers/global/config/parse/file.ts
+++ b/lib/workers/global/config/parse/file.ts
@@ -1,6 +1,5 @@
 import is from '@sindresorhus/is';
 import fs from 'fs-extra';
-import { load } from 'js-yaml';
 import JSON5 from 'json5';
 import upath from 'upath';
 import { migrateConfig } from '../../../../config/migration';
@@ -8,6 +7,7 @@ import type { AllConfig, RenovateConfig } from '../../../../config/types';
 import { logger } from '../../../../logger';
 import { parseJson } from '../../../../util/common';
 import { readSystemFile } from '../../../../util/fs';
+import { load } from '../../../../util/yaml';
 
 export async function getParsedContent(file: string): Promise<RenovateConfig> {
   if (upath.basename(file) === '.renovaterc') {
-- 
GitLab