From a56e0de4c7ed42dea3d9c014c93e1ec6547b3e64 Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Tue, 21 Jan 2020 18:04:50 +0100
Subject: [PATCH] feat: move localDir awareness to util/exec (#5207)

---
 lib/manager/poetry/artifacts.ts       | 30 ++++++++++----------
 lib/util/exec/index.ts                | 12 ++++++--
 lib/util/fs.ts                        | 40 +++++++++++++++++++++++++++
 lib/util/index.ts                     |  7 +++++
 lib/workers/global/index.ts           |  4 +--
 test/manager/poetry/artifacts.spec.ts | 10 +++----
 6 files changed, 79 insertions(+), 24 deletions(-)
 create mode 100644 lib/util/fs.ts
 create mode 100644 lib/util/index.ts

diff --git a/lib/manager/poetry/artifacts.ts b/lib/manager/poetry/artifacts.ts
index e935f71194..7e46960ccb 100644
--- a/lib/manager/poetry/artifacts.ts
+++ b/lib/manager/poetry/artifacts.ts
@@ -1,49 +1,51 @@
 import is from '@sindresorhus/is';
-import { parse, join } from 'upath';
-import { outputFile, readFile } from 'fs-extra';
 import { exec, ExecOptions } from '../../util/exec';
 import { logger } from '../../logger';
 import { UpdateArtifact, UpdateArtifactsResult } from '../common';
-import { platform } from '../../platform';
+import {
+  getSiblingFileName,
+  getSubDirectory,
+  readLocalFile,
+  writeLocalFile,
+} from '../../util/fs';
 
 export async function updateArtifacts({
   packageFileName,
   updatedDeps,
   newPackageFileContent,
-  config,
 }: UpdateArtifact): Promise<UpdateArtifactsResult[] | null> {
   logger.debug(`poetry.updateArtifacts(${packageFileName})`);
   if (!is.nonEmptyArray(updatedDeps)) {
     logger.debug('No updated poetry deps - returning null');
     return null;
   }
-  const subDirectory = parse(packageFileName).dir;
-  let lockFileName = join(subDirectory, 'poetry.lock');
-  let existingLockFileContent = await platform.getFile(lockFileName);
+  const subDirectory = getSubDirectory(packageFileName);
+  // Try poetry.lock first
+  let lockFileName = getSiblingFileName(packageFileName, 'poetry.lock');
+  let existingLockFileContent = await readLocalFile(lockFileName);
   if (!existingLockFileContent) {
-    lockFileName = join(subDirectory, 'pyproject.lock');
-    existingLockFileContent = await platform.getFile(lockFileName);
+    // Try pyproject.lock next
+    lockFileName = getSiblingFileName(packageFileName, 'pyproject.lock');
+    existingLockFileContent = await readLocalFile(lockFileName);
     if (!existingLockFileContent) {
       logger.debug(`No lock file found`);
       return null;
     }
   }
   logger.debug(`Updating ${lockFileName}`);
-  const localPackageFileName = join(config.localDir, packageFileName);
-  const localLockFileName = join(config.localDir, lockFileName);
   try {
-    await outputFile(localPackageFileName, newPackageFileContent);
+    await writeLocalFile(packageFileName, newPackageFileContent);
     const cmd: string[] = [];
     for (let i = 0; i < updatedDeps.length; i += 1) {
       const dep = updatedDeps[i];
       cmd.push(`poetry update --lock --no-interaction ${dep}`);
     }
     const execOptions: ExecOptions = {
-      cwd: join(config.localDir, subDirectory),
+      subDirectory,
       docker: { image: 'renovate/poetry' },
     };
     await exec(cmd, execOptions);
-    const newPoetryLockContent = await readFile(localLockFileName, 'utf8');
+    const newPoetryLockContent = await readLocalFile(lockFileName);
     if (existingLockFileContent === newPoetryLockContent) {
       logger.debug(`${lockFileName} is unchanged`);
       return null;
diff --git a/lib/util/exec/index.ts b/lib/util/exec/index.ts
index d47e37954c..e618acebe2 100644
--- a/lib/util/exec/index.ts
+++ b/lib/util/exec/index.ts
@@ -1,3 +1,4 @@
+import { join } from 'path';
 import { hrtime } from 'process';
 import { promisify } from 'util';
 import {
@@ -32,6 +33,7 @@ const pExec: (
 type ExtraEnv<T = unknown> = Record<string, T>;
 
 export interface ExecOptions extends ChildProcessExecOptions {
+  subDirectory?: string;
   extraEnv?: Opt<ExtraEnv>;
   docker?: Opt<DockerOptions>;
 }
@@ -77,13 +79,19 @@ export async function exec(
   cmd: string | string[],
   opts: ExecOptions = {}
 ): Promise<ExecResult> {
-  const { env, extraEnv, docker } = opts;
-  const cwd = opts.cwd || execConfig.localDir;
+  const { env, extraEnv, docker, subDirectory } = opts;
+  let cwd;
+  // istanbul ignore if
+  if (subDirectory) {
+    cwd = join(execConfig.localDir, subDirectory);
+  }
+  cwd = cwd || opts.cwd || execConfig.localDir;
   const childEnv = createChildEnv(env, extraEnv);
 
   const execOptions: ExecOptions = { ...opts };
   delete execOptions.extraEnv;
   delete execOptions.docker;
+  delete execOptions.subDirectory;
 
   const pExecOptions: ChildProcessExecOptions & { encoding: string } = {
     encoding: 'utf-8',
diff --git a/lib/util/fs.ts b/lib/util/fs.ts
new file mode 100644
index 0000000000..c297595505
--- /dev/null
+++ b/lib/util/fs.ts
@@ -0,0 +1,40 @@
+import { parse, join } from 'upath';
+import { outputFile, readFile } from 'fs-extra';
+import { logger } from '../logger';
+
+let localDir = '';
+
+export function setFsConfig(config: any): void {
+  localDir = config.localDir;
+}
+
+export function getSubDirectory(fileName: string): string {
+  return parse(fileName).dir;
+}
+
+export function getSiblingFileName(
+  existingFileNameWithPath: string,
+  otherFileName: string
+): string {
+  const subDirectory = getSubDirectory(existingFileNameWithPath);
+  return join(subDirectory, otherFileName);
+}
+
+export async function readLocalFile(fileName: string): Promise<string> {
+  const localFileName = join(localDir, fileName);
+  try {
+    const fileContent = await readFile(localFileName, 'utf8');
+    return fileContent;
+  } catch (err) /* istanbul ignore next */ {
+    logger.trace({ err }, 'Error reading local file');
+    return null;
+  }
+}
+
+export async function writeLocalFile(
+  fileName: string,
+  fileContent: string
+): Promise<void> {
+  const localFileName = join(localDir, fileName);
+  await outputFile(localFileName, fileContent);
+}
diff --git a/lib/util/index.ts b/lib/util/index.ts
new file mode 100644
index 0000000000..181fa5ee30
--- /dev/null
+++ b/lib/util/index.ts
@@ -0,0 +1,7 @@
+import { setExecConfig } from './exec';
+import { setFsConfig } from './fs';
+
+export function setUtilConfig(config: any): void {
+  setExecConfig(config);
+  setFsConfig(config);
+}
diff --git a/lib/workers/global/index.ts b/lib/workers/global/index.ts
index 0762814b5e..409b7e255a 100644
--- a/lib/workers/global/index.ts
+++ b/lib/workers/global/index.ts
@@ -12,7 +12,7 @@ import { initPlatform } from '../../platform';
 import * as hostRules from '../../util/host-rules';
 import { printStats } from '../../util/got/stats';
 import * as limits from './limits';
-import { setExecConfig } from '../../util/exec';
+import { setUtilConfig } from '../../util';
 
 type RenovateConfig = configParser.RenovateConfig;
 type RenovateRepository = configParser.RenovateRepository;
@@ -77,7 +77,7 @@ export async function start(): Promise<0 | 1> {
         break;
       }
       const repoConfig = await getRepositoryConfig(config, repository);
-      setExecConfig(repoConfig);
+      setUtilConfig(repoConfig);
       if (repoConfig.hostRules) {
         hostRules.clear();
         repoConfig.hostRules.forEach(rule => hostRules.add(rule));
diff --git a/test/manager/poetry/artifacts.spec.ts b/test/manager/poetry/artifacts.spec.ts
index c894f631e9..bb6097ee63 100644
--- a/test/manager/poetry/artifacts.spec.ts
+++ b/test/manager/poetry/artifacts.spec.ts
@@ -2,7 +2,6 @@ import { join } from 'upath';
 import _fs from 'fs-extra';
 import { exec as _exec } from 'child_process';
 import { updateArtifacts } from '../../../lib/manager/poetry/artifacts';
-import { platform as _platform } from '../../../lib/platform';
 import { mocked } from '../../util';
 import { envMock, mockExecAll } from '../../execUtil';
 import * as _env from '../../../lib/util/exec/env';
@@ -16,7 +15,6 @@ jest.mock('../../../lib/util/exec/env');
 const fs: jest.Mocked<typeof _fs> = _fs as any;
 const exec: jest.Mock<typeof _exec> = _exec as any;
 const env = mocked(_env);
-const platform = mocked(_platform);
 
 const config = {
   localDir: join('/tmp/github/some/repo'),
@@ -50,7 +48,7 @@ describe('.updateArtifacts()', () => {
     ).toBeNull();
   });
   it('returns null if unchanged', async () => {
-    platform.getFile.mockResolvedValueOnce('Current poetry.lock');
+    fs.readFile.mockReturnValueOnce('Current poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('Current poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -65,7 +63,7 @@ describe('.updateArtifacts()', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('returns updated poetry.lock', async () => {
-    platform.getFile.mockResolvedValueOnce('Old poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -85,7 +83,7 @@ describe('.updateArtifacts()', () => {
       binarySource: BinarySource.Docker,
       dockerUser: 'foobar',
     });
-    platform.getFile.mockResolvedValueOnce('Old poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Old poetry.lock' as any);
     const execSnapshots = mockExecAll(exec);
     fs.readFile.mockReturnValueOnce('New poetry.lock' as any);
     const updatedDeps = ['dep1'];
@@ -102,7 +100,7 @@ describe('.updateArtifacts()', () => {
     expect(execSnapshots).toMatchSnapshot();
   });
   it('catches errors', async () => {
-    platform.getFile.mockResolvedValueOnce('Current poetry.lock');
+    fs.readFile.mockResolvedValueOnce('Current poetry.lock' as any);
     fs.outputFile.mockImplementationOnce(() => {
       throw new Error('not found');
     });
-- 
GitLab