-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
RFC: feat(config): Use more conventional paths for config and data (#…
…5336) * feat(config): Use more conventional paths for config and data This implements: * Supporting user-defined environment variables adhering to the [XDG Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-0.6.html) to override yarn's defaults * Supporting equivalent environment variables on Windows such as %LOCALAPPDATA% * More conventional defaults for these locations according to the operating system. * Support for the user defined config dir in the `.yarnrc` lookup path * Storing global modules in a data-oriented location rather than a config location (seeing this is actually what motivated this PR) Concerns: * Existing Windows config locations will break. This probably need to be addressed with a migration path and/or a breaking change * A few notes included in comments (will highlight these with inline GH comments) * Unclear test status as master builds appears to fail on my laptop as well. Really interested in your feedback. I know [this has been attempted before](https://github.com/yarnpkg/yarn/pull/3674/files) -- cc @kelseasy -- and I'd really like to get this in! * Prettier * it -> test * Fall back to ~/.config/yarn instead of XDG/Windows paths
- Loading branch information
1 parent
18bed13
commit 2d454b5
Showing
4 changed files
with
193 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
/* @flow */ | ||
|
||
import {getDataDir, getCacheDir, getConfigDir} from '../../src/util/user-dirs'; | ||
import userHome from '../../src/util/user-home-dir'; | ||
|
||
const path = require('path'); | ||
|
||
describe('getDataDir', () => { | ||
describe('on windows', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('win32'); | ||
}); | ||
|
||
test('uses Yarn/data within LOCALAPPDATA if it exists', () => { | ||
process.env.LOCALAPPDATA = 'foo'; | ||
expect(getDataDir()).toBe(path.join('foo', 'Yarn', 'Data')); | ||
delete process.env.LOCALAPPDATA; | ||
}); | ||
|
||
test('uses the config dir otherwise', () => { | ||
expect(getDataDir()).toBe(path.join(userHome, '.config', 'yarn')); | ||
}); | ||
}); | ||
|
||
describe('on linux/darwin', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('linux'); | ||
}); | ||
|
||
test('uses XDG_DATA_HOME if it is set', () => { | ||
process.env.XDG_DATA_HOME = 'foo'; | ||
expect(getDataDir()).toBe(path.join('foo', 'yarn')); | ||
delete process.env.XDG_DATA_HOME; | ||
}); | ||
|
||
test('falls back to the config dir', () => { | ||
expect(getDataDir()).toBe(path.join(userHome, '.config', 'yarn')); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getCacheDir', () => { | ||
describe('on windows', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('win32'); | ||
}); | ||
|
||
test('uses Yarn\\Cache within LOCALAPPDATA if it exists', () => { | ||
process.env.LOCALAPPDATA = 'foo'; | ||
expect(getCacheDir()).toBe(path.join('foo', 'Yarn', 'Cache')); | ||
delete process.env.LOCALAPPDATA; | ||
}); | ||
|
||
test('uses AppData\\Local\\Cache otherwise', () => { | ||
expect(getCacheDir()).toBe(path.join(userHome, 'AppData', 'Local', 'Yarn', 'Cache')); | ||
}); | ||
}); | ||
|
||
describe('on darwin (macOS)', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('darwin'); | ||
}); | ||
|
||
test('uses XDG_CACHE_HOME if it is set', () => { | ||
process.env.XDG_CACHE_HOME = 'foo'; | ||
expect(getCacheDir()).toBe(path.join('foo', 'yarn')); | ||
delete process.env.XDG_CACHE_HOME; | ||
}); | ||
|
||
test('falls back to Library/Caches/Yarn', () => { | ||
expect(getCacheDir()).toBe(path.join(userHome, 'Library', 'Caches', 'Yarn')); | ||
}); | ||
}); | ||
|
||
describe('on others (linux, etc)', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('linux'); | ||
}); | ||
|
||
test('uses XDG_CACHE_HOME if it is set', () => { | ||
process.env.XDG_CACHE_HOME = 'foo'; | ||
expect(getCacheDir()).toBe(path.join('foo', 'yarn')); | ||
delete process.env.XDG_CACHE_HOME; | ||
}); | ||
|
||
test('falls back to .cache/yarn', () => { | ||
expect(getCacheDir()).toBe(path.join(userHome, '.cache', 'yarn')); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('getConfigDir', () => { | ||
describe('on windows', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('win32'); | ||
}); | ||
|
||
test('uses Yarn\\Config within LOCALAPPDATA if it exists', () => { | ||
process.env.LOCALAPPDATA = 'foo'; | ||
expect(getConfigDir()).toBe(path.join('foo', 'Yarn', 'Config')); | ||
delete process.env.LOCALAPPDATA; | ||
}); | ||
|
||
test('uses the config dir otherwise', () => { | ||
expect(getConfigDir()).toBe(path.join(userHome, '.config', 'yarn')); | ||
}); | ||
}); | ||
|
||
describe('on linux/darwin', () => { | ||
beforeEach(() => { | ||
mockProcessPlatform('linux'); | ||
}); | ||
|
||
test('uses XDG_CONFIG_HOME if it is set', () => { | ||
process.env.XDG_CONFIG_HOME = 'foo'; | ||
expect(getConfigDir()).toBe(path.join('foo', 'yarn')); | ||
delete process.env.XDG_CONFIG_HOME; | ||
}); | ||
|
||
test('falls back to .config/yarn', () => { | ||
expect(getConfigDir()).toBe(path.join(userHome, '.config', 'yarn')); | ||
}); | ||
}); | ||
}); | ||
|
||
function mockProcessPlatform(name: string) { | ||
// $FlowFixMe this is valid | ||
Object.defineProperty(process, 'platform', { | ||
get: jest.fn(() => name), | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
/* @flow */ | ||
|
||
const path = require('path'); | ||
const userHome = require('./user-home-dir').default; | ||
|
||
const FALLBACK_CONFIG_DIR = path.join(userHome, '.config', 'yarn'); | ||
const FALLBACK_CACHE_DIR = path.join(userHome, '.cache', 'yarn'); | ||
|
||
export function getDataDir(): string { | ||
if (process.platform === 'win32') { | ||
const WIN32_APPDATA_DIR = getLocalAppDataDir(); | ||
return WIN32_APPDATA_DIR == null ? FALLBACK_CONFIG_DIR : path.join(WIN32_APPDATA_DIR, 'Data'); | ||
} else if (process.env.XDG_DATA_HOME) { | ||
return path.join(process.env.XDG_DATA_HOME, 'yarn'); | ||
} else { | ||
// This could arguably be ~/Library/Application Support/Yarn on Macs, | ||
// but that feels unintuitive for a cli tool | ||
|
||
// Instead, use our prior fallback. Some day this could be | ||
// path.join(userHome, '.local', 'share', 'yarn') | ||
// or return path.join(WIN32_APPDATA_DIR, 'Data') on win32 | ||
return FALLBACK_CONFIG_DIR; | ||
} | ||
} | ||
|
||
export function getCacheDir(): string { | ||
if (process.platform === 'win32') { | ||
// process.env.TEMP also exists, but most apps put caches here | ||
return path.join(getLocalAppDataDir() || path.join(userHome, 'AppData', 'Local', 'Yarn'), 'Cache'); | ||
} else if (process.env.XDG_CACHE_HOME) { | ||
return path.join(process.env.XDG_CACHE_HOME, 'yarn'); | ||
} else if (process.platform === 'darwin') { | ||
return path.join(userHome, 'Library', 'Caches', 'Yarn'); | ||
} else { | ||
return FALLBACK_CACHE_DIR; | ||
} | ||
} | ||
|
||
export function getConfigDir(): string { | ||
if (process.platform === 'win32') { | ||
// Use our prior fallback. Some day this could be | ||
// return path.join(WIN32_APPDATA_DIR, 'Config') | ||
const WIN32_APPDATA_DIR = getLocalAppDataDir(); | ||
return WIN32_APPDATA_DIR == null ? FALLBACK_CONFIG_DIR : path.join(WIN32_APPDATA_DIR, 'Config'); | ||
} else if (process.env.XDG_CONFIG_HOME) { | ||
return path.join(process.env.XDG_CONFIG_HOME, 'yarn'); | ||
} else { | ||
return FALLBACK_CONFIG_DIR; | ||
} | ||
} | ||
|
||
function getLocalAppDataDir(): ?string { | ||
return process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Yarn') : null; | ||
} |