From 8c44d6bd10bb07ed44845d39f7576efb289e718a Mon Sep 17 00:00:00 2001
From: Simon Abbott <simon.abbott@airfordable.com>
Date: Fri, 20 Jan 2023 05:43:49 -0600
Subject: [PATCH] fix(manger/npm): apply config.npmrc during extraction, not in
 post-update (#19812)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
Closes https://github.com/renovatebot/renovate/issues/12891
---
 lib/modules/manager/npm/extract/index.spec.ts | 22 ++++++++---
 lib/modules/manager/npm/extract/index.ts      |  3 ++
 .../manager/npm/post-update/index.spec.ts     | 37 +++++++++++++++++++
 lib/modules/manager/npm/post-update/index.ts  |  2 +-
 4 files changed, 57 insertions(+), 7 deletions(-)

diff --git a/lib/modules/manager/npm/extract/index.spec.ts b/lib/modules/manager/npm/extract/index.spec.ts
index 7f90f02c3d..0529099f6f 100644
--- a/lib/modules/manager/npm/extract/index.spec.ts
+++ b/lib/modules/manager/npm/extract/index.spec.ts
@@ -165,25 +165,35 @@ describe('modules/manager/npm/extract/index', () => {
         'package.json',
         {}
       );
-      expect(res?.npmrc).toBeDefined();
+      expect(res?.npmrc).toBe('save-exact = true\n');
     });
 
-    it('ignores .npmrc when config.npmrc is defined and npmrcMerge=false', async () => {
+    it('uses config.npmrc if no .npmrc exists', async () => {
+      fs.readLocalFile = jest.fn(() => null);
+      const res = await npmExtract.extractPackageFile(
+        input01Content,
+        'package.json',
+        { ...defaultConfig, npmrc: 'config-npmrc' }
+      );
+      expect(res?.npmrc).toBe('config-npmrc');
+    });
+
+    it('uses config.npmrc if .npmrc does exist but npmrcMerge=false', async () => {
       fs.readLocalFile = jest.fn((fileName) => {
         if (fileName === '.npmrc') {
-          return 'some-npmrc\n';
+          return 'repo-npmrc\n';
         }
         return null;
       });
       const res = await npmExtract.extractPackageFile(
         input01Content,
         'package.json',
-        { npmrc: 'some-configured-npmrc' }
+        { npmrc: 'config-npmrc' }
       );
-      expect(res?.npmrc).toBeUndefined();
+      expect(res?.npmrc).toBe('config-npmrc');
     });
 
-    it('reads .npmrc when config.npmrc is merged', async () => {
+    it('merges config.npmrc and repo .npmrc when npmrcMerge=true', async () => {
       fs.readLocalFile = jest.fn((fileName) => {
         if (fileName === '.npmrc') {
           return 'repo-npmrc\n';
diff --git a/lib/modules/manager/npm/extract/index.ts b/lib/modules/manager/npm/extract/index.ts
index 89e3a71bc5..a69b98eac1 100644
--- a/lib/modules/manager/npm/extract/index.ts
+++ b/lib/modules/manager/npm/extract/index.ts
@@ -104,6 +104,7 @@ export async function extractPackageFile(
         { npmrcFileName },
         'Repo .npmrc file is ignored due to config.npmrc with config.npmrcMerge=false'
       );
+      npmrc = config.npmrc;
     } else {
       npmrc = config.npmrc ?? '';
       if (npmrc.length) {
@@ -130,6 +131,8 @@ export async function extractPackageFile(
       }
       npmrc += repoNpmrc;
     }
+  } else if (is.string(config.npmrc)) {
+    npmrc = config.npmrc;
   }
 
   const yarnrcYmlFileName = getSiblingFileName(fileName, '.yarnrc.yml');
diff --git a/lib/modules/manager/npm/post-update/index.spec.ts b/lib/modules/manager/npm/post-update/index.spec.ts
index 549e387449..dd2b971310 100644
--- a/lib/modules/manager/npm/post-update/index.spec.ts
+++ b/lib/modules/manager/npm/post-update/index.spec.ts
@@ -219,6 +219,43 @@ describe('modules/manager/npm/post-update/index', () => {
       ]);
     });
 
+    it('writes .npmrc files', async () => {
+      await writeExistingFiles(updateConfig, {
+        npm: [
+          // This package's npmrc should be written verbatim.
+          { packageFile: 'packages/core/package.json', npmrc: '#dummy' },
+          // No npmrc content should be written for this package.
+          { packageFile: 'packages/core/package.json' },
+        ],
+      });
+
+      expect(fs.writeLocalFile).toHaveBeenCalledOnce();
+      expect(fs.writeLocalFile).toHaveBeenCalledWith(
+        'packages/core/.npmrc',
+        '#dummy\n'
+      );
+    });
+
+    it('only sources npmrc content from package config', async () => {
+      await writeExistingFiles(
+        { ...updateConfig, npmrc: '#foobar' },
+        {
+          npm: [
+            // This package's npmrc should be written verbatim.
+            { packageFile: 'packages/core/package.json', npmrc: '#dummy' },
+            // No npmrc content should be written for this package.
+            { packageFile: 'packages/core/package.json' },
+          ],
+        }
+      );
+
+      expect(fs.writeLocalFile).toHaveBeenCalledOnce();
+      expect(fs.writeLocalFile).toHaveBeenCalledWith(
+        'packages/core/.npmrc',
+        '#dummy\n'
+      );
+    });
+
     it('works only on relevant folders', async () => {
       git.getFile.mockResolvedValueOnce(
         Fixtures.get('update-lockfile-massage-1/package-lock.json')
diff --git a/lib/modules/manager/npm/post-update/index.ts b/lib/modules/manager/npm/post-update/index.ts
index 0f15c4fc41..4eeb36b10c 100644
--- a/lib/modules/manager/npm/post-update/index.ts
+++ b/lib/modules/manager/npm/post-update/index.ts
@@ -143,7 +143,7 @@ export async function writeExistingFiles(
   for (const packageFile of npmFiles) {
     // TODO #7154
     const basedir = upath.dirname(packageFile.packageFile!);
-    const npmrc: string = packageFile.npmrc ?? config.npmrc;
+    const npmrc = packageFile.npmrc;
     const npmrcFilename = upath.join(basedir, '.npmrc');
     if (is.string(npmrc)) {
       try {
-- 
GitLab