From 68191f1ccf525db08dbc89ec9689ab7b107c0c44 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sun, 7 Mar 2021 17:38:42 +0100
Subject: [PATCH] feat(yarn): optimize yarn binary when binarySource=docker
 (#9013)

---
 .../__snapshots__/yarn.spec.ts.snap           | 14 +++++++
 lib/manager/npm/post-update/lerna.ts          |  4 +-
 lib/manager/npm/post-update/yarn.spec.ts      | 30 +++++++++++++-
 lib/manager/npm/post-update/yarn.ts           | 41 ++++++++++++-------
 4 files changed, 72 insertions(+), 17 deletions(-)

diff --git a/lib/manager/npm/post-update/__snapshots__/yarn.spec.ts.snap b/lib/manager/npm/post-update/__snapshots__/yarn.spec.ts.snap
index e6f1928aff..67beb2e582 100644
--- a/lib/manager/npm/post-update/__snapshots__/yarn.spec.ts.snap
+++ b/lib/manager/npm/post-update/__snapshots__/yarn.spec.ts.snap
@@ -24,6 +24,20 @@ Array [
 ]
 `;
 
+exports[`manager/npm/post-update/yarn checkYarnrc() returns no offline mirror and unquoted yarn path 1`] = `
+Object {
+  "offlineMirror": false,
+  "yarnPath": "./.yarn/cli.js",
+}
+`;
+
+exports[`manager/npm/post-update/yarn checkYarnrc() returns offline mirror and yarn path 1`] = `
+Object {
+  "offlineMirror": true,
+  "yarnPath": "./.yarn/cli.js",
+}
+`;
+
 exports[`manager/npm/post-update/yarn generates lock files using yarn v1.22.0 1`] = `
 Array [
   Object {
diff --git a/lib/manager/npm/post-update/lerna.ts b/lib/manager/npm/post-update/lerna.ts
index 3517e25afc..4e15adbf78 100644
--- a/lib/manager/npm/post-update/lerna.ts
+++ b/lib/manager/npm/post-update/lerna.ts
@@ -7,7 +7,7 @@ import { logger } from '../../../logger';
 import { ExecOptions, exec } from '../../../util/exec';
 import type { PackageFile, PostUpdateConfig } from '../../types';
 import { getNodeConstraint } from './node-version';
-import { optimizeCommand } from './yarn';
+import { getOptimizeCommand } from './yarn';
 
 export interface GenerateLockFileResult {
   error?: boolean;
@@ -53,7 +53,7 @@ export async function generateLockFiles(
       }
       preCommands.push(installYarn);
       if (skipInstalls !== false) {
-        preCommands.push(optimizeCommand);
+        preCommands.push(getOptimizeCommand());
       }
       cmdOptions = '--ignore-scripts --ignore-engines --ignore-platform';
     } else if (lernaClient === 'npm') {
diff --git a/lib/manager/npm/post-update/yarn.spec.ts b/lib/manager/npm/post-update/yarn.spec.ts
index 5b2dd6afd5..0274362190 100644
--- a/lib/manager/npm/post-update/yarn.spec.ts
+++ b/lib/manager/npm/post-update/yarn.spec.ts
@@ -45,7 +45,9 @@ describe(getName(__filename), () => {
       });
       fs.readFile.mockImplementation((filename, encoding) => {
         if (filename.endsWith('.yarnrc')) {
-          return new Promise<string>((resolve) => resolve(null));
+          return new Promise<string>((resolve) =>
+            resolve('yarn-path ./.yarn/cli.js\n')
+          );
         }
         return new Promise<string>((resolve) =>
           resolve('package-lock-contents')
@@ -167,4 +169,30 @@ describe(getName(__filename), () => {
     expect(res.lockFile).not.toBeDefined();
     expect(fixSnapshots(execSnapshots)).toMatchSnapshot();
   });
+  describe('checkYarnrc()', () => {
+    it('returns offline mirror and yarn path', async () => {
+      fs.readFile.mockImplementation((filename, encoding) => {
+        if (filename.endsWith('.yarnrc')) {
+          return new Promise<string>((resolve) =>
+            resolve(
+              'yarn-offline-mirror "./packages-cache"\nyarn-path "./.yarn/cli.js"\n'
+            )
+          );
+        }
+        return new Promise<string>((resolve) => resolve(''));
+      });
+      expect(await _yarnHelper.checkYarnrc('/tmp/renovate')).toMatchSnapshot();
+    });
+    it('returns no offline mirror and unquoted yarn path', async () => {
+      fs.readFile.mockImplementation((filename, encoding) => {
+        if (filename.endsWith('.yarnrc')) {
+          return new Promise<string>((resolve) =>
+            resolve('yarn-path ./.yarn/cli.js\n')
+          );
+        }
+        return new Promise<string>((resolve) => resolve(''));
+      });
+      expect(await _yarnHelper.checkYarnrc('/tmp/renovate')).toMatchSnapshot();
+    });
+  });
 });
diff --git a/lib/manager/npm/post-update/yarn.ts b/lib/manager/npm/post-update/yarn.ts
index 6a1b8e3dbd..861759cff4 100644
--- a/lib/manager/npm/post-update/yarn.ts
+++ b/lib/manager/npm/post-update/yarn.ts
@@ -21,25 +21,36 @@ export interface GenerateLockFileResult {
   stderr?: string;
 }
 
-export async function hasYarnOfflineMirror(cwd: string): Promise<boolean> {
+export async function checkYarnrc(
+  cwd: string
+): Promise<{ offlineMirror: boolean; yarnPath: string | null }> {
+  let offlineMirror = false;
+  let yarnPath: string = null;
   try {
     const yarnrc = await readFile(`${cwd}/.yarnrc`, 'utf8');
     if (is.string(yarnrc)) {
       const mirrorLine = yarnrc
         .split('\n')
         .find((line) => line.startsWith('yarn-offline-mirror '));
-      if (mirrorLine) {
-        return true;
+      offlineMirror = !!mirrorLine;
+      const pathLine = yarnrc
+        .split('\n')
+        .find((line) => line.startsWith('yarn-path '));
+      if (pathLine) {
+        yarnPath = pathLine.replace(/^yarn-path\s+"?(.+?)"?$/, '$1');
       }
     }
   } catch (err) /* istanbul ignore next */ {
     // not found
   }
-  return false;
+  return { offlineMirror, yarnPath };
 }
 
-export const optimizeCommand =
-  "sed -i 's/ steps,/ steps.slice(0,1),/' /home/ubuntu/.npm-global/lib/node_modules/yarn/lib/cli.js";
+export function getOptimizeCommand(
+  fileName = '/home/ubuntu/.npm-global/lib/node_modules/yarn/lib/cli.js'
+): string {
+  return `sed -i 's/ steps,/ steps.slice(0,1),/' ${quote(fileName)}`;
+}
 
 export async function generateLockFile(
   cwd: string,
@@ -71,14 +82,16 @@ export async function generateLockFile(
       CI: 'true',
     };
 
-    if (
-      isYarn1 &&
-      config.skipInstalls !== false &&
-      (await hasYarnOfflineMirror(cwd)) === false
-    ) {
-      logger.debug('Updating yarn.lock only - skipping node_modules');
-      // The following change causes Yarn 1.x to exit gracefully after updating the lock file but without installing node_modules
-      preCommands.push(optimizeCommand);
+    if (isYarn1 && config.skipInstalls !== false) {
+      const { offlineMirror, yarnPath } = await checkYarnrc(cwd);
+      if (!offlineMirror) {
+        logger.debug('Updating yarn.lock only - skipping node_modules');
+        // The following change causes Yarn 1.x to exit gracefully after updating the lock file but without installing node_modules
+        preCommands.push(getOptimizeCommand());
+        if (yarnPath) {
+          preCommands.push(getOptimizeCommand(yarnPath) + ' || true');
+        }
+      }
     }
     const commands = [];
     let cmdOptions = '';
-- 
GitLab