From a54db557121ddb64c81cde0fd1735c541747dd2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Thu, 10 Aug 2017 12:27:05 +0100 Subject: [PATCH 1/9] Fallbacks to /tmp if the preferred cache folder isn't writable --- src/config.js | 24 +++++++++++++++++++++--- src/constants.js | 15 +++++++++++---- src/reporters/lang/en.js | 4 ++++ 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/src/config.js b/src/config.js index 447fc81fb9..854fc533bb 100644 --- a/src/config.js +++ b/src/config.js @@ -276,9 +276,27 @@ export default class Config { networkConcurrency: this.networkConcurrency, networkTimeout: this.networkTimeout, }); - this._cacheRootFolder = String( - opts.cacheFolder || this.getOption('cache-folder', true) || constants.MODULE_CACHE_DIRECTORY, - ); + + let cacheRootFolder = opts.cacheFolder || this.getOption('cache-folder', true); + + for (let t = 0; t < constants.PREFERRED_MODULE_CACHE_DIRECTORIES.length && !cacheRootFolder; ++t) { + const tentativeCacheFolder = constants.PREFERRED_MODULE_CACHE_DIRECTORIES[t]; + + try { + await fs.mkdirp(tentativeCacheFolder); + await fs.writeFile(path.join(tentativeCacheFolder, constants.WTEST_FILENAME), `testing write access`); + cacheRootFolder = tentativeCacheFolder; + } catch (error) { + this.reporter.warn(this.reporter.lang('cacheFolderSkipped', tentativeCacheFolder)); + } + } + + if (!cacheRootFolder) { + throw new MessageError(this.reporter.lang('cacheFolderMissing')); + } else { + this._cacheRootFolder = String(cacheRootFolder); + } + this.workspacesEnabled = Boolean(this.getOption('workspaces-experimental')); this.pruneOfflineMirror = Boolean(this.getOption('yarn-offline-mirror-pruning')); diff --git a/src/constants.js b/src/constants.js index b6296bb6e9..f112413770 100644 --- a/src/constants.js +++ b/src/constants.js @@ -46,15 +46,21 @@ function getDirectory(category: string): string { return path.join(userHome, `.${category}`, 'yarn'); } -function getCacheDirectory(): string { +function getPreferredCacheDirectories(): Array { + const preferredCacheDirectories = [getDirectory('cache')]; + + if (process.platform !== 'win32') { + preferredCacheDirectories.unshift('/tmp/.yarn-cache'); + } + if (process.platform === 'darwin') { - return path.join(userHome, 'Library', 'Caches', 'Yarn'); + preferredCacheDirectories.unshift(path.join(userHome, 'Library', 'Caches', 'Yarn')); } - return getDirectory('cache'); + return preferredCacheDirectories; } -export const MODULE_CACHE_DIRECTORY = getCacheDirectory(); +export const PREFERRED_MODULE_CACHE_DIRECTORIES = getPreferredCacheDirectories(); export const CONFIG_DIRECTORY = getDirectory('config'); export const LINK_REGISTRY_DIRECTORY = path.join(CONFIG_DIRECTORY, 'link'); export const GLOBAL_MODULE_DIRECTORY = path.join(CONFIG_DIRECTORY, 'global'); @@ -70,6 +76,7 @@ export const LOCKFILE_FILENAME = 'yarn.lock'; export const METADATA_FILENAME = '.yarn-metadata.json'; export const TARBALL_FILENAME = '.yarn-tarball.tgz'; export const CLEAN_FILENAME = '.yarnclean'; +export const WTEST_FILENAME = 'yarn-wtest'; export const DEFAULT_INDENT = ' '; export const SINGLE_INSTANCE_PORT = 31997; diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index bde78aff1d..31ec4d9f5c 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -181,6 +181,10 @@ const messages = { workspaceNameMandatory: 'Missing name in workspace at $0, ignoring.', workspaceNameDuplicate: 'There are more than one workspace with name $0', + cacheFolderSkipped: 'Skipping preferred cache folder $0 because it is not writable.', + cacheFolderMissing: + "Yarn hasn't been able to find a cache folder. Please use an explicit --cache-folder option to tell it what location to use, or make one of the preferred locations writable.", + execMissingCommand: 'Missing command name.', commandNotSpecified: 'No command specified.', From 5a3f279df0a0987cd8b3d32dee731c29ae601dd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 11 Aug 2017 14:08:25 +0100 Subject: [PATCH 2/9] Adds a preferred-cache-folder option --- src/cli/index.js | 2 ++ src/config.js | 32 +++++++++++++++++++++++--------- src/constants.js | 16 ++++++++++------ src/reporters/lang/en.js | 1 + src/util/fs.js | 2 ++ 5 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/cli/index.js b/src/cli/index.js index a1e6975504..7d8e6bf7ac 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -60,6 +60,7 @@ export function main({ '--modules-folder ', 'rather than installing modules into the node_modules folder relative to the cwd, output them here', ); + commander.option('--preferred-cache-folder ', 'specify a custom folder to store the yarn cache if possible'); commander.option('--cache-folder ', 'specify a custom folder to store the yarn cache'); commander.option('--mutex [:specifier]', 'use a mutex to ensure only one yarn instance is executing'); commander.option('--emoji [bool]', 'enable emoji in output', process.platform === 'darwin'); @@ -324,6 +325,7 @@ export function main({ binLinks: commander.binLinks, modulesFolder: commander.modulesFolder, globalFolder: commander.globalFolder, + preferredCacheFolder: commander.preferredCacheFolder, cacheFolder: commander.cacheFolder, preferOffline: commander.preferOffline, captureHar: commander.har, diff --git a/src/config.js b/src/config.js index 854fc533bb..41d87f92d8 100644 --- a/src/config.js +++ b/src/config.js @@ -279,15 +279,29 @@ export default class Config { let cacheRootFolder = opts.cacheFolder || this.getOption('cache-folder', true); - for (let t = 0; t < constants.PREFERRED_MODULE_CACHE_DIRECTORIES.length && !cacheRootFolder; ++t) { - const tentativeCacheFolder = constants.PREFERRED_MODULE_CACHE_DIRECTORIES[t]; - - try { - await fs.mkdirp(tentativeCacheFolder); - await fs.writeFile(path.join(tentativeCacheFolder, constants.WTEST_FILENAME), `testing write access`); - cacheRootFolder = tentativeCacheFolder; - } catch (error) { - this.reporter.warn(this.reporter.lang('cacheFolderSkipped', tentativeCacheFolder)); + if (!cacheRootFolder) { + let preferredCacheFolders = constants.PREFERRED_MODULE_CACHE_DIRECTORIES; + const preferredCacheFolder = opts.preferredCacheFolder || this.getOption('preferred-cache-folder', true); + + if (preferredCacheFolder) { + preferredCacheFolders = [preferredCacheFolder].concat(preferredCacheFolders); + } + + for (let t = 0; t < preferredCacheFolders.length && !cacheRootFolder; ++t) { + const tentativeCacheFolder = preferredCacheFolders[t]; + + try { + await fs.mkdirp(tentativeCacheFolder); + // eslint-disable-next-line + await fs.access(tentativeCacheFolder, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK); + cacheRootFolder = tentativeCacheFolder; + } catch (error) { + this.reporter.warn(this.reporter.lang('cacheFolderSkipped', tentativeCacheFolder)); + } + + if (cacheRootFolder && t > 0) { + this.reporter.warn(this.reporter.lang('cacheFolderSelected', cacheRootFolder)); + } } } diff --git a/src/constants.js b/src/constants.js index f112413770..5a0c2795dc 100644 --- a/src/constants.js +++ b/src/constants.js @@ -1,5 +1,6 @@ /* @flow */ +const os = require('os'); const path = require('path'); const userHome = require('./util/user-home-dir').default; @@ -47,14 +48,17 @@ function getDirectory(category: string): string { } function getPreferredCacheDirectories(): Array { - const preferredCacheDirectories = [getDirectory('cache')]; + const preferredCacheDirectories = []; - if (process.platform !== 'win32') { - preferredCacheDirectories.unshift('/tmp/.yarn-cache'); + if (process.platform === 'darwin') { + preferredCacheDirectories.push(path.join(userHome, 'Library', 'Caches', 'Yarn')); } - if (process.platform === 'darwin') { - preferredCacheDirectories.unshift(path.join(userHome, 'Library', 'Caches', 'Yarn')); + preferredCacheDirectories.push(getDirectory('cache')); + + if (process.platform !== 'win32') { + preferredCacheDirectories.push(path.join(os.tmpdir(), '.yarn-cache')); + preferredCacheDirectories.push('/tmp/.yarn-cache'); } return preferredCacheDirectories; @@ -76,7 +80,7 @@ export const LOCKFILE_FILENAME = 'yarn.lock'; export const METADATA_FILENAME = '.yarn-metadata.json'; export const TARBALL_FILENAME = '.yarn-tarball.tgz'; export const CLEAN_FILENAME = '.yarnclean'; -export const WTEST_FILENAME = 'yarn-wtest'; +export const ACCESS_FILENAME = '.yarn-access'; export const DEFAULT_INDENT = ' '; export const SINGLE_INSTANCE_PORT = 31997; diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index 31ec4d9f5c..6deef0434b 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -184,6 +184,7 @@ const messages = { cacheFolderSkipped: 'Skipping preferred cache folder $0 because it is not writable.', cacheFolderMissing: "Yarn hasn't been able to find a cache folder. Please use an explicit --cache-folder option to tell it what location to use, or make one of the preferred locations writable.", + cacheFolderSelected: 'Selected the next writable cache folder in the list, will be $0.', execMissingCommand: 'Missing command name.', diff --git a/src/util/fs.js b/src/util/fs.js index 3fa97c011d..107656a7b5 100644 --- a/src/util/fs.js +++ b/src/util/fs.js @@ -13,6 +13,8 @@ const globModule = require('glob'); const os = require('os'); const path = require('path'); +export const constants = fs.constants; + export const lockQueue = new BlockingQueue('fs lock'); export const readFileBuffer = promisify(fs.readFile); From 38679b618225072353cd4ea0425ca1497da7c14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 11 Aug 2017 15:03:29 +0100 Subject: [PATCH 3/9] Adds an integration test --- __tests__/integration.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/__tests__/integration.js b/__tests__/integration.js index dd7a3d89ec..b56886d237 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -70,3 +70,20 @@ test('--mutex network', async () => { execa(command, ['add', 'foo'].concat(args), options), ]); }); + +test('cache folder fallback', async () => { + const cwd = await makeTemp(); + const cacheFolder = path.join(cwd, '.cache'); + + await fs.mkdirp(cacheFolder); + await fs.chmod(cacheFolder, 0o000); + + const command = path.resolve(__dirname, '../bin/yarn'); + const args = ['--preferred-cache-folder', cacheFolder]; + + const options = {cwd}; + + await Promise.all([ + execa(command, ['cache', 'dir'].concat(args), options), + ]); +}); From eaaeeef9c43fdff76b727911dd812ebd4dedb7c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 11 Aug 2017 15:41:29 +0100 Subject: [PATCH 4/9] Node 4 doesn't expose fs.constants --- src/util/fs.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/util/fs.js b/src/util/fs.js index 107656a7b5..93255669db 100644 --- a/src/util/fs.js +++ b/src/util/fs.js @@ -13,7 +13,11 @@ const globModule = require('glob'); const os = require('os'); const path = require('path'); -export const constants = fs.constants; +export const constants = typeof fs.constants !== 'undefined' ? fs.constants : { + R_OK: fs.R_OK, + W_OK: fs.W_OK, + X_OK: fs.X_OK, +}; export const lockQueue = new BlockingQueue('fs lock'); From 80504bcc6cbc09aa7b305030f84b8141969ae2ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 11 Aug 2017 16:28:59 +0100 Subject: [PATCH 5/9] Remove the /tmp fallback --- src/constants.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/constants.js b/src/constants.js index 5a0c2795dc..2736c83d47 100644 --- a/src/constants.js +++ b/src/constants.js @@ -52,14 +52,11 @@ function getPreferredCacheDirectories(): Array { if (process.platform === 'darwin') { preferredCacheDirectories.push(path.join(userHome, 'Library', 'Caches', 'Yarn')); + } else { + preferredCacheDirectories.push(getDirectory('cache')); } - preferredCacheDirectories.push(getDirectory('cache')); - - if (process.platform !== 'win32') { - preferredCacheDirectories.push(path.join(os.tmpdir(), '.yarn-cache')); - preferredCacheDirectories.push('/tmp/.yarn-cache'); - } + preferredCacheDirectories.push(path.join(os.tmpdir(), '.yarn-cache')); return preferredCacheDirectories; } From 3324612c1a6b81707961e1ced525ee6ba09ec258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 11 Aug 2017 16:30:59 +0100 Subject: [PATCH 6/9] Lints --- __tests__/integration.js | 4 +--- src/util/fs.js | 13 ++++++++----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/__tests__/integration.js b/__tests__/integration.js index b56886d237..6b4c6ecedf 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -83,7 +83,5 @@ test('cache folder fallback', async () => { const options = {cwd}; - await Promise.all([ - execa(command, ['cache', 'dir'].concat(args), options), - ]); + await Promise.all([execa(command, ['cache', 'dir'].concat(args), options)]); }); diff --git a/src/util/fs.js b/src/util/fs.js index 93255669db..25647d436c 100644 --- a/src/util/fs.js +++ b/src/util/fs.js @@ -13,11 +13,14 @@ const globModule = require('glob'); const os = require('os'); const path = require('path'); -export const constants = typeof fs.constants !== 'undefined' ? fs.constants : { - R_OK: fs.R_OK, - W_OK: fs.W_OK, - X_OK: fs.X_OK, -}; +export const constants = + typeof fs.constants !== 'undefined' + ? fs.constants + : { + R_OK: fs.R_OK, + W_OK: fs.W_OK, + X_OK: fs.X_OK, + }; export const lockQueue = new BlockingQueue('fs lock'); From eec72018d7328e0b9942df42d09105bc52c21cb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Mon, 14 Aug 2017 11:35:19 +0100 Subject: [PATCH 7/9] Adds a test --- __tests__/integration.js | 31 +++++++++++++++++++++-- src/cli/commands/cache.js | 2 +- src/cli/index.js | 2 +- src/config.js | 2 +- src/reporters/base-reporter.js | 2 +- src/reporters/console/console-reporter.js | 8 +++--- src/reporters/lang/en.js | 2 +- src/util/misc.js | 18 +++++++++++++ 8 files changed, 56 insertions(+), 11 deletions(-) diff --git a/__tests__/integration.js b/__tests__/integration.js index 6b4c6ecedf..9d5a28832c 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -4,6 +4,8 @@ import execa from 'execa'; import makeTemp from './_temp.js'; import * as fs from '../src/util/fs.js'; +import * as misc from '../src/util/misc.js'; +import * as constants from '../src/constants.js'; jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000; @@ -76,12 +78,37 @@ test('cache folder fallback', async () => { const cacheFolder = path.join(cwd, '.cache'); await fs.mkdirp(cacheFolder); - await fs.chmod(cacheFolder, 0o000); const command = path.resolve(__dirname, '../bin/yarn'); const args = ['--preferred-cache-folder', cacheFolder]; const options = {cwd}; - await Promise.all([execa(command, ['cache', 'dir'].concat(args), options)]); + { + const {stderr, stdout} = execa(command, ['cache', 'dir'].concat(args), options); + + const stdoutPromise = misc.consumeStream(stdout); + const stderrPromise = misc.consumeStream(stderr); + + const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]); + + expect(stdoutOutput.toString().trim()).toEqual(path.join(cacheFolder, `v${constants.CACHE_VERSION}`)); + expect(stderrOutput.toString()).not.toMatch(/Skipping preferred cache folder/); + } + + await fs.chmod(cacheFolder, 0o000); + + { + const {stderr, stdout} = execa(command, ['cache', 'dir'].concat(args), options); + + const stdoutPromise = misc.consumeStream(stdout); + const stderrPromise = misc.consumeStream(stderr); + + const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]); + + expect(stdoutOutput.toString().trim()).toEqual( + path.join(constants.PREFERRED_MODULE_CACHE_DIRECTORIES[0], `v${constants.CACHE_VERSION}`), + ); + expect(stderrOutput.toString()).toMatch(/Skipping preferred cache folder/); + } }); diff --git a/src/cli/commands/cache.js b/src/cli/commands/cache.js index 7afa4dfdc0..9f09363397 100644 --- a/src/cli/commands/cache.js +++ b/src/cli/commands/cache.js @@ -55,7 +55,7 @@ export const {run, setFlags, examples} = buildSubCommands('cache', { }, dir(config: Config, reporter: Reporter) { - reporter.log(config.cacheFolder); + reporter.log(config.cacheFolder, {force: true}); }, async clean(config: Config, reporter: Reporter, flags: Object, args: Array): Promise { diff --git a/src/cli/index.js b/src/cli/index.js index 7d8e6bf7ac..1cdb04f0d8 100644 --- a/src/cli/index.js +++ b/src/cli/index.js @@ -61,7 +61,7 @@ export function main({ 'rather than installing modules into the node_modules folder relative to the cwd, output them here', ); commander.option('--preferred-cache-folder ', 'specify a custom folder to store the yarn cache if possible'); - commander.option('--cache-folder ', 'specify a custom folder to store the yarn cache'); + commander.option('--cache-folder ', 'specify a custom folder that must be used to store the yarn cache'); commander.option('--mutex [:specifier]', 'use a mutex to ensure only one yarn instance is executing'); commander.option('--emoji [bool]', 'enable emoji in output', process.platform === 'darwin'); commander.option('-s, --silent', 'skip Yarn console logs, other types of logs (script output) will be printed'); diff --git a/src/config.js b/src/config.js index 41d87f92d8..d3b7162235 100644 --- a/src/config.js +++ b/src/config.js @@ -288,7 +288,7 @@ export default class Config { } for (let t = 0; t < preferredCacheFolders.length && !cacheRootFolder; ++t) { - const tentativeCacheFolder = preferredCacheFolders[t]; + const tentativeCacheFolder = String(preferredCacheFolders[t]); try { await fs.mkdirp(tentativeCacheFolder); diff --git a/src/reporters/base-reporter.js b/src/reporters/base-reporter.js index 4b46563c75..f78d9b0e8f 100644 --- a/src/reporters/base-reporter.js +++ b/src/reporters/base-reporter.js @@ -195,7 +195,7 @@ export default class BaseReporter { success(message: string) {} // a simple log message - log(message: string) {} + log(message: string, {force = false}: {force?: boolean} = {}) {} // a shell command has been executed command(command: string) {} diff --git a/src/reporters/console/console-reporter.js b/src/reporters/console/console-reporter.js index e319b60db0..c487d0504d 100644 --- a/src/reporters/console/console-reporter.js +++ b/src/reporters/console/console-reporter.js @@ -160,13 +160,13 @@ export default class ConsoleReporter extends BaseReporter { this.log(this._prependEmoji(msg, '✨')); } - log(msg: string) { + log(msg: string, {force = false}: {force?: boolean} = {}) { this._lastCategorySize = 0; - this._log(msg); + this._log(msg, {force}); } - _log(msg: string) { - if (this.isSilent) { + _log(msg: string, {force = false}: {force?: boolean} = {}) { + if (this.isSilent && !force) { return; } clearLine(this.stdout); diff --git a/src/reporters/lang/en.js b/src/reporters/lang/en.js index 6deef0434b..6e4cfb1f40 100644 --- a/src/reporters/lang/en.js +++ b/src/reporters/lang/en.js @@ -183,7 +183,7 @@ const messages = { cacheFolderSkipped: 'Skipping preferred cache folder $0 because it is not writable.', cacheFolderMissing: - "Yarn hasn't been able to find a cache folder. Please use an explicit --cache-folder option to tell it what location to use, or make one of the preferred locations writable.", + "Yarn hasn't been able to find a cache folder it can use. Please use the explicit --cache-folder option to tell it what location to use, or make one of the preferred locations writable.", cacheFolderSelected: 'Selected the next writable cache folder in the list, will be $0.', execMissingCommand: 'Missing command name.', diff --git a/src/util/misc.js b/src/util/misc.js index 440ec2f98d..1725a666ce 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -2,6 +2,24 @@ const _camelCase = require('camelcase'); +export function consumeStream(stream: Object): Promise { + return new Promise((resolve, reject) => { + const buffers = []; + + stream.on(`data`, buffer => { + buffers.push(buffer); + }); + + stream.on(`end`, () => { + resolve(Buffer.concat(buffers)); + }); + + stream.on(`error`, error => { + reject(error); + }); + }); +} + export function sortAlpha(a: string, b: string): number { // sort alphabetically in a deterministic way const shortLen = Math.min(a.length, b.length); From 1a0791b962978bc558c74016e24285b46c512dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Tue, 15 Aug 2017 17:36:22 +0100 Subject: [PATCH 8/9] wip --- __tests__/integration.js | 28 ++++++++++------------------ src/config.js | 12 ++++++++++-- src/reporters/base-reporter.js | 1 + 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/__tests__/integration.js b/__tests__/integration.js index 9d5a28832c..3ab6da0a24 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -77,38 +77,30 @@ test('cache folder fallback', async () => { const cwd = await makeTemp(); const cacheFolder = path.join(cwd, '.cache'); - await fs.mkdirp(cacheFolder); - const command = path.resolve(__dirname, '../bin/yarn'); const args = ['--preferred-cache-folder', cacheFolder]; const options = {cwd}; - { + function runCacheDir(): Promise> { const {stderr, stdout} = execa(command, ['cache', 'dir'].concat(args), options); const stdoutPromise = misc.consumeStream(stdout); const stderrPromise = misc.consumeStream(stderr); - const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]); - - expect(stdoutOutput.toString().trim()).toEqual(path.join(cacheFolder, `v${constants.CACHE_VERSION}`)); - expect(stderrOutput.toString()).not.toMatch(/Skipping preferred cache folder/); + return Promise.all([stdoutPromise, stderrPromise]); } - await fs.chmod(cacheFolder, 0o000); + const [stdoutOutput, stderrOutput] = await runCacheDir(); - { - const {stderr, stdout} = execa(command, ['cache', 'dir'].concat(args), options); + expect(stdoutOutput.toString().trim()).toEqual(path.join(cacheFolder, `v${constants.CACHE_VERSION}`)); + expect(stderrOutput.toString()).not.toMatch(/Skipping preferred cache folder/); - const stdoutPromise = misc.consumeStream(stdout); - const stderrPromise = misc.consumeStream(stderr); + await fs.unlink(cacheFolder); + await fs.writeFile(cacheFolder, `not a directory`); - const [stdoutOutput, stderrOutput] = await Promise.all([stdoutPromise, stderrPromise]); + const [stdoutOutput2, stderrOutput2] = await runCacheDir(); - expect(stdoutOutput.toString().trim()).toEqual( - path.join(constants.PREFERRED_MODULE_CACHE_DIRECTORIES[0], `v${constants.CACHE_VERSION}`), - ); - expect(stderrOutput.toString()).toMatch(/Skipping preferred cache folder/); - } + expect(stdoutOutput2.toString().trim()).toEqual(path.join(constants.PREFERRED_MODULE_CACHE_DIRECTORIES[0], `v${constants.CACHE_VERSION}`)); + expect(stderrOutput2.toString()).toMatch(/Skipping preferred cache folder/); }); diff --git a/src/config.js b/src/config.js index d3b7162235..5740d80087 100644 --- a/src/config.js +++ b/src/config.js @@ -291,10 +291,18 @@ export default class Config { const tentativeCacheFolder = String(preferredCacheFolders[t]); try { + await fs.mkdirp(tentativeCacheFolder); - // eslint-disable-next-line - await fs.access(tentativeCacheFolder, fs.constants.R_OK | fs.constants.W_OK | fs.constants.X_OK); + + const testFile = path.join(tentativeCacheFolder, 'testfile'); + + // fs.access is not enough, because the cache folder could actually be a file. + await fs.writeFile(testFile, 'content'); + await fs.readFile(testFile); + await fs.unlink(testFile); + cacheRootFolder = tentativeCacheFolder; + } catch (error) { this.reporter.warn(this.reporter.lang('cacheFolderSkipped', tentativeCacheFolder)); } diff --git a/src/reporters/base-reporter.js b/src/reporters/base-reporter.js index f78d9b0e8f..1bde601b87 100644 --- a/src/reporters/base-reporter.js +++ b/src/reporters/base-reporter.js @@ -195,6 +195,7 @@ export default class BaseReporter { success(message: string) {} // a simple log message + // TODO: rethink the {force} parameter. In the meantime, please don't use it (cf comments in #4143). log(message: string, {force = false}: {force?: boolean} = {}) {} // a shell command has been executed From 6c522e814981f71efcf75bd4baa94f7746ccc3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Thu, 17 Aug 2017 14:03:21 +0100 Subject: [PATCH 9/9] Lints --- __tests__/integration.js | 4 +++- src/config.js | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/integration.js b/__tests__/integration.js index 3ab6da0a24..6dad4f6e18 100644 --- a/__tests__/integration.js +++ b/__tests__/integration.js @@ -101,6 +101,8 @@ test('cache folder fallback', async () => { const [stdoutOutput2, stderrOutput2] = await runCacheDir(); - expect(stdoutOutput2.toString().trim()).toEqual(path.join(constants.PREFERRED_MODULE_CACHE_DIRECTORIES[0], `v${constants.CACHE_VERSION}`)); + expect(stdoutOutput2.toString().trim()).toEqual( + path.join(constants.PREFERRED_MODULE_CACHE_DIRECTORIES[0], `v${constants.CACHE_VERSION}`), + ); expect(stderrOutput2.toString()).toMatch(/Skipping preferred cache folder/); }); diff --git a/src/config.js b/src/config.js index 5740d80087..068c9e251d 100644 --- a/src/config.js +++ b/src/config.js @@ -291,7 +291,6 @@ export default class Config { const tentativeCacheFolder = String(preferredCacheFolders[t]); try { - await fs.mkdirp(tentativeCacheFolder); const testFile = path.join(tentativeCacheFolder, 'testfile'); @@ -302,7 +301,6 @@ export default class Config { await fs.unlink(testFile); cacheRootFolder = tentativeCacheFolder; - } catch (error) { this.reporter.warn(this.reporter.lang('cacheFolderSkipped', tentativeCacheFolder)); }