From 40dbc86f2347bcd66476ad85f4ec5a56357fb860 Mon Sep 17 00:00:00 2001
From: Jay Lindquist <jay.lindquist@gmail.com>
Date: Mon, 3 Jun 2024 14:12:10 -0500
Subject: [PATCH] fix(yarn): search parent directories for yarn configuration
 (#29415)

---
 lib/modules/manager/npm/extract/index.spec.ts | 37 +++++++++++++++++++
 lib/modules/manager/npm/extract/index.ts      | 28 +++++++++++---
 2 files changed, 59 insertions(+), 6 deletions(-)

diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts
index 07bf5524a7..032faf0a18 100644
--- a/lib/modules/manager/npm/extract/index.spec.ts
+++ b/lib/modules/manager/npm/extract/index.spec.ts
@@ -260,6 +260,15 @@ describe('modules/manager/npm/extract/index', () => {
     });
 
     it('reads registryUrls from .yarnrc.yml', async () => {
+      fs.findLocalSiblingOrParent.mockImplementation(
+        (packageFile, otherFile): Promise<string | null> => {
+          if (packageFile === 'package.json' && otherFile === '.yarnrc.yml') {
+            return Promise.resolve('.yarnrc.yml');
+          }
+          return Promise.resolve(null);
+        },
+      );
+
       fs.readLocalFile.mockImplementation((fileName): Promise<any> => {
         if (fileName === '.yarnrc.yml') {
           return Promise.resolve(
@@ -279,12 +288,22 @@ describe('modules/manager/npm/extract/index', () => {
     });
 
     it('reads registryUrls from .yarnrc', async () => {
+      fs.findLocalSiblingOrParent.mockImplementation(
+        (packageFile, otherFile): Promise<string | null> => {
+          if (packageFile === 'package.json' && otherFile === '.yarnrc') {
+            return Promise.resolve('.yarnrc');
+          }
+          return Promise.resolve(null);
+        },
+      );
+
       fs.readLocalFile.mockImplementation((fileName): Promise<any> => {
         if (fileName === '.yarnrc') {
           return Promise.resolve('registry "https://registry.example.com"');
         }
         return Promise.resolve(null);
       });
+
       const res = await npmExtract.extractPackageFile(
         input02Content,
         'package.json',
@@ -296,6 +315,15 @@ describe('modules/manager/npm/extract/index', () => {
     });
 
     it('resolves registry URLs using the package name if set', async () => {
+      fs.findLocalSiblingOrParent.mockImplementation(
+        (packageFile, otherFile): Promise<string | null> => {
+          if (packageFile === 'package.json' && otherFile === '.yarnrc.yml') {
+            return Promise.resolve('.yarnrc.yml');
+          }
+          return Promise.resolve(null);
+        },
+      );
+
       fs.readLocalFile.mockImplementation((fileName): Promise<any> => {
         if (fileName === '.yarnrc.yml') {
           return Promise.resolve(codeBlock`
@@ -773,6 +801,15 @@ describe('modules/manager/npm/extract/index', () => {
     });
 
     it('sets skipInstalls false if Yarn zero-install is used', async () => {
+      fs.findLocalSiblingOrParent.mockImplementation(
+        (packageFile, otherFile): Promise<string | null> => {
+          if (packageFile === 'package.json' && otherFile === '.yarnrc.yml') {
+            return Promise.resolve('.yarnrc.yml');
+          }
+          return Promise.resolve(null);
+        },
+      );
+
       fs.readLocalFile.mockImplementation((fileName): Promise<any> => {
         if (fileName === 'yarn.lock') {
           return Promise.resolve('# yarn.lock');
diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts
index a6606e1fb7..f9ed88faa9 100644
--- a/lib/modules/manager/npm/extract/index.ts
+++ b/lib/modules/manager/npm/extract/index.ts
@@ -1,7 +1,11 @@
 import is from '@sindresorhus/is';
 import { GlobalConfig } from '../../../../config/global';
 import { logger } from '../../../../logger';
-import { getSiblingFileName, readLocalFile } from '../../../../util/fs';
+import {
+  findLocalSiblingOrParent,
+  getSiblingFileName,
+  readLocalFile,
+} from '../../../../util/fs';
 import { newlineRegex, regEx } from '../../../../util/regex';
 import { NpmDatasource } from '../../../datasource/npm';
 
@@ -122,17 +126,29 @@ export async function extractPackageFile(
     npmrc = config.npmrc;
   }
 
-  const yarnrcYmlFileName = getSiblingFileName(packageFile, '.yarnrc.yml');
-  const yarnZeroInstall = await isZeroInstall(yarnrcYmlFileName);
+  const yarnrcYmlFileName = await findLocalSiblingOrParent(
+    packageFile,
+    '.yarnrc.yml',
+  );
+  const yarnZeroInstall = yarnrcYmlFileName
+    ? await isZeroInstall(yarnrcYmlFileName)
+    : false;
 
   let yarnConfig: YarnConfig | null = null;
-  const repoYarnrcYml = await readLocalFile(yarnrcYmlFileName, 'utf8');
+  const repoYarnrcYml = yarnrcYmlFileName
+    ? await readLocalFile(yarnrcYmlFileName, 'utf8')
+    : null;
   if (is.string(repoYarnrcYml) && repoYarnrcYml.trim().length > 0) {
     yarnConfig = loadConfigFromYarnrcYml(repoYarnrcYml);
   }
 
-  const legacyYarnrcFileName = getSiblingFileName(packageFile, '.yarnrc');
-  const repoLegacyYarnrc = await readLocalFile(legacyYarnrcFileName, 'utf8');
+  const legacyYarnrcFileName = await findLocalSiblingOrParent(
+    packageFile,
+    '.yarnrc',
+  );
+  const repoLegacyYarnrc = legacyYarnrcFileName
+    ? await readLocalFile(legacyYarnrcFileName, 'utf8')
+    : null;
   if (is.string(repoLegacyYarnrc) && repoLegacyYarnrc.trim().length > 0) {
     yarnConfig = loadConfigFromLegacyYarnrc(repoLegacyYarnrc);
   }
-- 
GitLab