From b7ec7b3afd6f135b113b262fe20caf7380ec3276 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 7 Apr 2017 12:27:15 +0100 Subject: [PATCH 1/6] Checks that the webpack builds are working properly (#3064) --- circle.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/circle.yml b/circle.yml index 246b68f03a..627a035d4f 100644 --- a/circle.yml +++ b/circle.yml @@ -33,6 +33,7 @@ test: - yarn check-lockfile - yarn build-dist - node ./scripts/build-webpack.js + - ls -t1 artifacts | head -n2 | while read entry; do node "./artifacts/$entry" --version; done - ./scripts/build-deb.sh # Test that installing as root works and that it also works # behind a user namespace which Circle CI tests are run under From f8b1f676cdb5aef997c57fa927754065a5020905 Mon Sep 17 00:00:00 2001 From: blexrob Date: Fri, 7 Apr 2017 13:41:11 +0200 Subject: [PATCH 2/6] Fix missing subdeps in production install when those are present in devDependencies list (#2921) * Test for install skipping subdependencies when those are named in root devDependencies * Add 'incompatible' flag to references, use that to ignore incompatible packages instead of faulty inherit logic. Fixes #2819 * Unconditionally mark packages as ignored, hoister now fully corrects transitive uses --- __tests__/commands/install/integration.js | 19 ++++++++ .../a/package.json | 7 +++ .../b/c/package.json | 4 ++ .../b/package.json | 7 +++ .../package.json | 8 ++++ __tests__/package-hoister.js | 2 +- src/cli/commands/install.js | 10 ++--- src/package-compatibility.js | 1 + src/package-hoister.js | 45 ++++++++++--------- src/package-install-scripts.js | 1 + src/package-reference.js | 2 + 11 files changed, 77 insertions(+), 29 deletions(-) create mode 100644 __tests__/fixtures/install/install-prod-deduped-direct-dev-dep/a/package.json create mode 100644 __tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/c/package.json create mode 100644 __tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/package.json create mode 100644 __tests__/fixtures/install/install-prod-deduped-direct-dev-dep/package.json diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index c38cdd3fbf..bf2673ae40 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -157,6 +157,25 @@ test.concurrent("production mode with deduped dev dep shouldn't be removed", asy }); }); +test.concurrent("production mode dep on package in dev deps shouldn't be removed", async () => { + await runInstall({production: true}, 'install-prod-deduped-direct-dev-dep', async (config) => { + assert.equal( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'a', 'package.json'))).version, + '1.0.0', + ); + + assert.equal( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'b', 'package.json'))).version, + '1.0.0', + ); + + assert.equal( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'c', 'package.json'))).version, + '1.0.0', + ); + }); +}); + test.concurrent('hoisting should factor ignored dependencies', async () => { // you should only modify this test if you know what you're doing // when we calculate hoisting we need to factor in ignored dependencies in it diff --git a/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/a/package.json b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/a/package.json new file mode 100644 index 0000000000..be5b70f271 --- /dev/null +++ b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/a/package.json @@ -0,0 +1,7 @@ +{ + "name": "a", + "version": "1.0.0", + "dependencies": { + "b": "file:../b" + } +} diff --git a/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/c/package.json b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/c/package.json new file mode 100644 index 0000000000..abd3384930 --- /dev/null +++ b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/c/package.json @@ -0,0 +1,4 @@ +{ + "name": "c", + "version": "1.0.0" +} diff --git a/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/package.json b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/package.json new file mode 100644 index 0000000000..018eedcd05 --- /dev/null +++ b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/b/package.json @@ -0,0 +1,7 @@ +{ + "name": "b", + "version": "1.0.0", + "dependencies": { + "c": "file:c" + } +} diff --git a/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/package.json b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/package.json new file mode 100644 index 0000000000..b08417d92b --- /dev/null +++ b/__tests__/fixtures/install/install-prod-deduped-direct-dev-dep/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "a": "file:a" + }, + "devDependencies": { + "b": "file:b" + } +} diff --git a/__tests__/package-hoister.js b/__tests__/package-hoister.js index a1698b8aeb..d36819743f 100644 --- a/__tests__/package-hoister.js +++ b/__tests__/package-hoister.js @@ -110,7 +110,7 @@ test('Produces valid destination paths for scoped modules', () => { _reference: (({}: any): PackageReference), }: any): Manifest); - const info = new HoistManifest(key, parts, pkg, '', false, false); + const info = new HoistManifest(key, parts, pkg, '', true, false); const tree = new Map([ ['@scoped/dep', info], diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js index ffeeba1ff4..2e75934eab 100644 --- a/src/cli/commands/install.js +++ b/src/cli/commands/install.js @@ -252,6 +252,7 @@ export class Install { pattern += '@' + depMap[name]; } + // normalization made sure packages are mentioned only once if (isUsed) { usedPatterns.push(pattern); } else { @@ -357,12 +358,9 @@ export class Install { const ref = manifest._reference; invariant(ref, 'expected package reference'); - if (ref.requests.length === 1) { - // this module was only depended on once by the root so we can safely ignore it - // if it was requested more than once then ignoring it would break a transitive - // dep that resolved to it - ref.ignore = true; - } + // just mark the package as ignored. if the package is used by a required package, the hoister + // will take care of that. + ref.ignore = true; } } diff --git a/src/package-compatibility.js b/src/package-compatibility.js index fd6ab8d6b9..39acff4e49 100644 --- a/src/package-compatibility.js +++ b/src/package-compatibility.js @@ -126,6 +126,7 @@ export default class PackageCompatibility { if (ref.optional) { ref.ignore = true; + ref.incompatible = true; reporter.warn(`${human}: ${msg}`); if (!didIgnore) { diff --git a/src/package-hoister.js b/src/package-hoister.js index facc004879..5234557ab8 100644 --- a/src/package-hoister.js +++ b/src/package-hoister.js @@ -13,9 +13,9 @@ type Parts = Array; let historyCounter = 0; export class HoistManifest { - constructor(key: string, parts: Parts, pkg: Manifest, loc: string, isIgnored: boolean, inheritIsIgnored: boolean) { - this.isIgnored = isIgnored; - this.inheritIsIgnored = inheritIsIgnored; + constructor(key: string, parts: Parts, pkg: Manifest, loc: string, isRequired: boolean, isIncompatible: boolean) { + this.isRequired = isRequired; + this.isIncompatible = isIncompatible; this.loc = loc; this.pkg = pkg; @@ -28,8 +28,8 @@ export class HoistManifest { this.addHistory(`Start position = ${key}`); } - isIgnored: boolean; - inheritIsIgnored: boolean; + isRequired: boolean; + isIncompatible: boolean; pkg: Manifest; loc: string; parts: Parts; @@ -96,7 +96,7 @@ export default class PackageHoister { while (true) { let queue = this.levelQueue; if (!queue.length) { - this._propagateNonIgnored(); + this._propagateRequired(); return; } @@ -135,8 +135,8 @@ export default class PackageHoister { // let parentParts: Parts = []; - let isIgnored = ref.ignore; - let inheritIsIgnored = false; + const isIncompatible = ref.incompatible; + let isRequired = !parent && !ref.ignore && !isIncompatible; if (parent) { if (!this.tree.get(parent.key)) { @@ -144,9 +144,8 @@ export default class PackageHoister { } // non ignored dependencies inherit parent's ignored status // parent may transition from ignored to non ignored when hoisted if it is used in another non ignored branch - if (!isIgnored && parent.isIgnored) { - isIgnored = parent.isIgnored; - inheritIsIgnored = true; + if (!isRequired && !isIncompatible && parent.isRequired) { + isRequired = true; } parentParts = parent.parts; } @@ -155,7 +154,7 @@ export default class PackageHoister { const loc: string = this.config.generateHardModulePath(ref); const parts = parentParts.concat(pkg.name); const key: string = this.implodeKey(parts); - const info: HoistManifest = new HoistManifest(key, parts, pkg, loc, isIgnored, inheritIsIgnored); + const info: HoistManifest = new HoistManifest(key, parts, pkg, loc, isRequired, isIncompatible); // this.tree.set(key, info); @@ -173,13 +172,13 @@ export default class PackageHoister { * Propagate inherited ignore statuses from non-ignored to ignored packages */ - _propagateNonIgnored() { + _propagateRequired() { // const toVisit: Array = []; // enumerate all non-ignored packages for (const entry of this.tree.entries()) { - if (!entry[1].isIgnored) { + if (entry[1].isRequired) { toVisit.push(entry[1]); } } @@ -192,9 +191,9 @@ export default class PackageHoister { for (const depPattern of ref.dependencies) { const depinfo = this._lookupDependency(info, depPattern); - if (depinfo && depinfo.isIgnored && depinfo.inheritIsIgnored) { - depinfo.isIgnored = false; - info.addHistory(`Mark as non-ignored because of usage by ${info.key}`); + if (depinfo && !depinfo.isRequired && !depinfo.isIncompatible) { + depinfo.isRequired = true; + depinfo.addHistory(`Mark as non-ignored because of usage by ${info.key}`); toVisit.push(depinfo); } } @@ -247,12 +246,14 @@ export default class PackageHoister { const existing = this.tree.get(checkKey); if (existing) { if (existing.loc === info.loc) { - // switch to non ignored if earlier deduped version was ignored - if (existing.isIgnored && !info.isIgnored) { - existing.isIgnored = info.isIgnored; + // switch to non ignored if earlier deduped version was ignored (must be compatible) + if (!existing.isRequired && info.isRequired) { + existing.addHistory(`Deduped ${fullKey} to this item, marking as required`); + existing.isRequired = true; + } else { + existing.addHistory(`Deduped ${fullKey} to this item`); } - existing.addHistory(`Deduped ${fullKey} to this item`); return {parts: checkParts, duplicate: true}; } else { // everything above will be shadowed and this is a conflict @@ -543,7 +544,7 @@ export default class PackageHoister { const ref = info.pkg._reference; invariant(ref, 'expected reference'); - if (info.isIgnored) { + if (!info.isRequired) { info.addHistory('Deleted as this module was ignored'); } else { visibleFlatTree.push([loc, info]); diff --git a/src/package-install-scripts.js b/src/package-install-scripts.js index 4cfcc5f32b..2fcdcdab31 100644 --- a/src/package-install-scripts.js +++ b/src/package-install-scripts.js @@ -111,6 +111,7 @@ export default class PackageInstallScripts { if (ref.optional) { ref.ignore = true; + ref.incompatible = true; this.reporter.warn(this.reporter.lang('optionalModuleScriptFail', err.message)); this.reporter.info(this.reporter.lang('optionalModuleFail')); diff --git a/src/package-reference.js b/src/package-reference.js index 7f72e37c71..6ba2433ddd 100644 --- a/src/package-reference.js +++ b/src/package-reference.js @@ -33,6 +33,7 @@ export default class PackageReference { this.optional = null; this.root = false; this.ignore = false; + this.incompatible = false; this.fresh = false; this.location = null; this.addRequest(request); @@ -48,6 +49,7 @@ export default class PackageReference { uid: string; optional: ?boolean; ignore: boolean; + incompatible: boolean; fresh: boolean; dependencies: Array; patterns: Array; From fd98cb98549c55ae79a761e709417ae3e997effa Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 7 Apr 2017 14:40:21 +0100 Subject: [PATCH 3/6] replaced deprecated asserts (#3069) --- __tests__/commands/install/integration.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index bf2673ae40..f2f160bad6 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -159,20 +159,17 @@ test.concurrent("production mode with deduped dev dep shouldn't be removed", asy test.concurrent("production mode dep on package in dev deps shouldn't be removed", async () => { await runInstall({production: true}, 'install-prod-deduped-direct-dev-dep', async (config) => { - assert.equal( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'a', 'package.json'))).version, - '1.0.0', - ); + expect( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'a', 'package.json'))).version + ).toEqual('1.0.0'); - assert.equal( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'b', 'package.json'))).version, - '1.0.0', - ); + expect( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'b', 'package.json'))).version + ).toEqual('1.0.0'); - assert.equal( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'c', 'package.json'))).version, - '1.0.0', - ); + expect( + (await fs.readJson(path.join(config.cwd, 'node_modules', 'c', 'package.json'))).version + ).toEqual('1.0.0'); }); }); From 8d3d80924f77df534ce6d08b87b7fb1418551dee Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 7 Apr 2017 14:48:54 +0100 Subject: [PATCH 4/6] fixing lint (#3070) --- __tests__/commands/install/integration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index f2f160bad6..8175937be7 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -160,15 +160,15 @@ test.concurrent("production mode with deduped dev dep shouldn't be removed", asy test.concurrent("production mode dep on package in dev deps shouldn't be removed", async () => { await runInstall({production: true}, 'install-prod-deduped-direct-dev-dep', async (config) => { expect( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'a', 'package.json'))).version + (await fs.readJson(path.join(config.cwd, 'node_modules', 'a', 'package.json'))).version, ).toEqual('1.0.0'); expect( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'b', 'package.json'))).version + (await fs.readJson(path.join(config.cwd, 'node_modules', 'b', 'package.json'))).version, ).toEqual('1.0.0'); expect( - (await fs.readJson(path.join(config.cwd, 'node_modules', 'c', 'package.json'))).version + (await fs.readJson(path.join(config.cwd, 'node_modules', 'c', 'package.json'))).version, ).toEqual('1.0.0'); }); }); From 63f4f2c0a0ff1fd8e7ebf9357123f92379cbf70f Mon Sep 17 00:00:00 2001 From: Konstantin Raev Date: Fri, 7 Apr 2017 15:05:11 +0100 Subject: [PATCH 5/6] Fixes integrity check for --production flag (#3067) * fixed integrity check when running with --production * added test * removed unused var --- __tests__/commands/install/integration.js | 12 ++++++++++++ __tests__/fixtures/install/bailout-prod/package.json | 8 ++++++++ __tests__/fixtures/install/bailout-prod/yarn.lock | 11 +++++++++++ src/cli/commands/install.js | 3 +-- 4 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 __tests__/fixtures/install/bailout-prod/package.json create mode 100644 __tests__/fixtures/install/bailout-prod/yarn.lock diff --git a/__tests__/commands/install/integration.js b/__tests__/commands/install/integration.js index 8175937be7..6e0f7c79b5 100644 --- a/__tests__/commands/install/integration.js +++ b/__tests__/commands/install/integration.js @@ -816,3 +816,15 @@ test.concurrent('prunes the offline mirror after pruning is enabled', (): Promis expect(await fs.exists(path.join(config.cwd, `${mirrorPath}/dep-b-1.0.0.tgz`))).toEqual(false); }); }); + +test.concurrent('bailout should work with --production flag too', (): Promise => { + return runInstall({production: true}, 'bailout-prod', async (config, reporter): Promise => { + // remove file + await fs.unlink(path.join(config.cwd, 'node_modules', 'left-pad', 'index.js')); + // run install again + const reinstall = new Install({production: true}, config, reporter, await Lockfile.fromDirectory(config.cwd)); + await reinstall.init(); + // don't expect file being recreated because install should have bailed out + expect(await fs.exists(path.join(config.cwd, 'node_modules', 'left-pad', 'index.js'))).toBe(false); + }); +}); diff --git a/__tests__/fixtures/install/bailout-prod/package.json b/__tests__/fixtures/install/bailout-prod/package.json new file mode 100644 index 0000000000..47b670d504 --- /dev/null +++ b/__tests__/fixtures/install/bailout-prod/package.json @@ -0,0 +1,8 @@ +{ + "dependencies": { + "left-pad": "^1.1.3" + }, + "devDependencies": { + "is-array": "^1.0.1" + } +} diff --git a/__tests__/fixtures/install/bailout-prod/yarn.lock b/__tests__/fixtures/install/bailout-prod/yarn.lock new file mode 100644 index 0000000000..83f2533fdc --- /dev/null +++ b/__tests__/fixtures/install/bailout-prod/yarn.lock @@ -0,0 +1,11 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +is-array@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-array/-/is-array-1.0.1.tgz#e9850cc2cc860c3bc0977e84ccf0dd464584279a" + +left-pad@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.1.3.tgz#612f61c033f3a9e08e939f1caebeea41b6f3199a" diff --git a/src/cli/commands/install.js b/src/cli/commands/install.js index 2e75934eab..a58251c0eb 100644 --- a/src/cli/commands/install.js +++ b/src/cli/commands/install.js @@ -383,7 +383,6 @@ export class Install { requests: depRequests, patterns: rawPatterns, ignorePatterns, - usedPatterns, } = await this.fetchRequestFromCwd(); let topLevelPatterns: Array = []; @@ -392,7 +391,7 @@ export class Install { await this.resolver.init(this.prepareRequests(depRequests), this.flags.flat); topLevelPatterns = this.preparePatterns(rawPatterns); flattenedTopLevelPatterns = await this.flatten(topLevelPatterns); - return {bailout: await this.bailout(usedPatterns)}; + return {bailout: await this.bailout(topLevelPatterns)}; }); steps.push(async (curr: number, total: number) => { From 0287c5076bd41ee435720dba72c47876881fb577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ma=C3=ABl=20Nison?= Date: Fri, 7 Apr 2017 15:28:55 +0100 Subject: [PATCH 6/6] Remove the dependency on the "rc" module (#3063) * Removes dependency on the "rc" module * Removers shebang-loader, not used anymore * Fixes flow errors * Fixes tests on Windows --- __tests__/lifecycle-scripts.js | 12 +++---- package.json | 1 - scripts/build-webpack.js | 2 +- src/rc.js | 16 +++------ src/util/rc.js | 64 ++++++++++++++++++++++++++++++++++ yarn.lock | 9 ----- 6 files changed, 76 insertions(+), 28 deletions(-) create mode 100644 src/util/rc.js diff --git a/__tests__/lifecycle-scripts.js b/__tests__/lifecycle-scripts.js index 178ace9cdc..51e77633f1 100644 --- a/__tests__/lifecycle-scripts.js +++ b/__tests__/lifecycle-scripts.js @@ -32,28 +32,28 @@ async function execCommand(cmd: string, packageName: string, env = process.env): test('should add the global yarnrc arguments to the command line', async () => { const stdout = await execCommand('cache dir', 'yarnrc-cli'); - expect(stdout.replace(/\\/g, '/')).toMatch(/^\/tmp\/foobar\/v[0-9]+\n$/); + expect(stdout.replace(/\\/g, '/')).toMatch(/^(C:)?\/tmp\/foobar\/v[0-9]+\n$/); }); test('should add the command-specific yarnrc arguments to the command line if the command name matches', async () => { const stdout = await execCommand('cache dir', 'yarnrc-cli-command-specific-ok'); - expect(stdout.replace(/\\/g, '/')).toMatch(/^\/tmp\/foobar\/v[0-9]+\n$/); + expect(stdout.replace(/\\/g, '/')).toMatch(/^(C:)?\/tmp\/foobar\/v[0-9]+\n$/); }); test('should not add the command-specific yarnrc arguments if the command name doesn\'t match', async () => { const stdout = await execCommand('cache dir', 'yarnrc-cli-command-specific-ko'); - expect(stdout.replace(/\\/g, '/')).not.toMatch(/^\/tmp\/foobar\/v[0-9]+\n$/); + expect(stdout.replace(/\\/g, '/')).not.toMatch(/^(C:)?\/tmp\/foobar\/v[0-9]+\n$/); }); test('should allow overriding the yarnrc values from the command line', async () => { const stdout = await execCommand('cache dir --cache-folder /tmp/toto', 'yarnrc-cli'); - expect(stdout.replace(/\\/g, '/')).toMatch(/^\/tmp\/toto\/v[0-9]+\n$/); + expect(stdout.replace(/\\/g, '/')).toMatch(/^(C:)?\/tmp\/toto\/v[0-9]+\n$/); }); // Test disabled for now, cf rc.js -test.skip('should resolve the yarnrc values relative to where the file lives', async () => { +test('should resolve the yarnrc values relative to where the file lives', async () => { const stdout = await execCommand('cache dir', 'yarnrc-cli-relative'); - expect(stdout.replace(/\\/g, '/')).toMatch(/^(\/[^\/]+)+\/foobar\/hello\/world\/v[0-9]+\n$/); + expect(stdout.replace(/\\/g, '/')).toMatch(/^(C:)?(\/[^\/]+)+\/foobar\/hello\/world\/v[0-9]+\n$/); }); test('should expose `npm_config_argv` environment variable to lifecycle scripts for back compatibility with npm (#684)', diff --git a/package.json b/package.json index 4fbc207909..587f9694da 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "node-gyp": "^3.2.1", "object-path": "^0.11.2", "proper-lockfile": "^2.0.0", - "rc": "^1.2.1", "read": "^1.0.7", "request": "^2.81.0", "request-capture-har": "^1.2.2", diff --git a/scripts/build-webpack.js b/scripts/build-webpack.js index 0aede020c7..b94d2aa7e2 100755 --- a/scripts/build-webpack.js +++ b/scripts/build-webpack.js @@ -56,7 +56,7 @@ const compilerLegacy = webpack({ exclude: /node_modules/, loader: 'babel-loader', query: babelRc.env['pre-node5'], - }], + }] }, plugins: [ new webpack.BannerPlugin({ diff --git a/src/rc.js b/src/rc.js index 9624537dee..29ae25f4b3 100644 --- a/src/rc.js +++ b/src/rc.js @@ -1,8 +1,8 @@ /* @flow */ +import {dirname, resolve} from 'path'; import parse from './lockfile/parse.js'; - -const rc = require('rc'); +import * as rcUtil from './util/rc.js'; // Keys that will get resolved relative to the path of the rc file they belong to const PATH_KEYS = [ @@ -14,20 +14,14 @@ const PATH_KEYS = [ let rcConfCache; let rcArgsCache; -const buildRcConf = () => rc('yarn', {}, [], (fileText) => { +const buildRcConf = () => rcUtil.findRc('yarn', (fileText, filePath) => { const values = parse(fileText, 'yarnrc'); const keys = Object.keys(values); - // Unfortunately, the "rc" module we use doesn't tell us the file path :( - // cf https://github.com/dominictarr/rc/issues/61 - for (const key of keys) { for (const pathKey of PATH_KEYS) { - if (key.replace(/^(--)?([^.]+\.)+/, '') === pathKey) { - // values[key] = resolve(dirname(filePath), values[key]); - if (!values[key].startsWith('/')) { - delete values[keys]; - } + if (key.replace(/^(--)?([^.]+\.)*/, '') === pathKey) { + values[key] = resolve(dirname(filePath), values[key]); } } } diff --git a/src/util/rc.js b/src/util/rc.js new file mode 100644 index 0000000000..3692a910de --- /dev/null +++ b/src/util/rc.js @@ -0,0 +1,64 @@ +/* @flow */ + +import {readFileSync} from 'fs'; +import {basename, dirname, join} from 'path'; + +const etc = '/etc'; +const isWin = process.platform === 'win32'; +const home = isWin ? process.env.USERPROFILE : process.env.HOME; + +export function findRc(name: string, parser: Function): Object { + let configPaths = []; + + function addConfigPath(... segments) { + configPaths.push(join(... segments)); + } + + function addRecursiveConfigPath(... segments) { + const queue = []; + + let oldPath; + let path = join(... segments); + + do { + queue.unshift(path); + + oldPath = path; + path = join(dirname(dirname(path)), basename(path)); + } while (path !== oldPath); + + configPaths = configPaths.concat(queue); + } + + function fetchConfigs(): Object { + return Object.assign({}, ... configPaths.map((path) => { + try { + return parser(readFileSync(path).toString(), path); + } catch (error) { + return {}; + } + })); + } + + if (!isWin) { + addConfigPath(etc, name, 'config'); + addConfigPath(etc, `${name}rc`); + } + + if (home) { + addConfigPath(home, '.config', name, 'config'); + addConfigPath(home, '.config', name); + addConfigPath(home, `.${name}`, 'config'); + addConfigPath(home, `.${name}rc`); + } + + addRecursiveConfigPath(process.cwd(), `.${name}rc`); + + const envVariable = `${name}_config`.toUpperCase(); + + if (process.env[envVariable]) { + addConfigPath(process.env[envVariable]); + } + + return fetchConfigs(); +} diff --git a/yarn.lock b/yarn.lock index fc9498fde2..23fbc50120 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3719,15 +3719,6 @@ randombytes@^2.0.0, randombytes@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" -rc@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" - dependencies: - deep-extend "~0.4.0" - ini "~1.3.0" - minimist "^1.2.0" - strip-json-comments "~2.0.1" - rc@~1.1.6: version "1.1.7" resolved "https://registry.yarnpkg.com/rc/-/rc-1.1.7.tgz#c5ea564bb07aff9fd3a5b32e906c1d3a65940fea"