Skip to content

Commit

Permalink
Merge pull request #503 from connectdotz/yarn-lookup-fix
Browse files Browse the repository at this point in the history
fixed using wrong package manager in yarn workspace projects
  • Loading branch information
fson committed Mar 6, 2018
2 parents e531d1a + ed1603c commit a86f49f
Show file tree
Hide file tree
Showing 10 changed files with 2,564 additions and 31 deletions.
10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
{
"private": true,
"scripts": {
"format": "prettier --single-quote --trailing-comma=es5 --print-width=100 --write '{create-react-native-app,react-native-scripts}/{src,template}/**/*.js'"
"format": "prettier --single-quote --trailing-comma=es5 --print-width=100 --write '{create-react-native-app,react-native-scripts}/{src,template}/**/*.js'",
"test": "jest"
},
"devDependencies": {
"jest": "^21.2.1",
"prettier": "^0.21.0"
},
"jest": {
"testPathIgnorePatterns": [
"node_modules",
"template"
]
}
}
43 changes: 43 additions & 0 deletions react-native-scripts/__mocks__/fs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// based on jest official doc: https://facebook.github.io/jest/docs/en/manual-mocks.html
// __mocks__/fs.js
'use strict';

const path = require('path');

const fs = jest.genMockFromModule('fs');

// This is a custom function that our tests can use during setup to specify
// what the files on the "mock" filesystem should look like when any of the
// `fs` APIs are used.
let mockFiles = Object.create(null);
function __setMockFiles(newMockFiles) {
mockFiles = Object.create(null);
for (const file in newMockFiles) {
const dir = path.dirname(file);
if (!mockFiles[dir]) {
mockFiles[dir] = [];
}
mockFiles[dir].push(path.basename(file));
}
}

// A custom version of `readdirSync` that reads from the special mocked out
// file list set via __setMockFiles
function readdirSync(directoryPath) {
return mockFiles[directoryPath] || [];
}

function accessSync(directoryPath) {
const dir = path.dirname(directoryPath);
const children = mockFiles[dir];

if (!children || children.indexOf(path.basename(directoryPath)) < 0) {
throw new TypeError(`no such file/dir: ${directoryPath}`);
}
}

fs.__setMockFiles = __setMockFiles;
fs.readdirSync = readdirSync;
fs.accessSync = accessSync;

module.exports = fs;
3 changes: 2 additions & 1 deletion react-native-scripts/src/scripts/eject.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import path from 'path';
import rimraf from 'rimraf';
import spawn from 'cross-spawn';
import log from '../util/log';
import { hasYarn } from '../util/pm';

import { detach } from '../util/expo';

Expand Down Expand Up @@ -78,7 +79,7 @@ Ejecting is permanent! Please be careful with your selection.
const { ejectMethod } = await inquirer.prompt(questions);

if (ejectMethod === 'raw') {
const useYarn = await fse.exists(path.resolve('yarn.lock'));
const useYarn = hasYarn(process.cwd());
const npmOrYarn = useYarn ? 'yarn' : 'npm';
const appJson = JSON.parse(await fse.readFile(path.resolve('app.json')));
const pkgJson = JSON.parse(await fse.readFile(path.resolve('package.json')));
Expand Down
3 changes: 2 additions & 1 deletion react-native-scripts/src/scripts/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import pathExists from 'path-exists';
import spawn from 'cross-spawn';
import log from '../util/log';
import install from '../util/install';
import { hasYarn } from '../util/pm';

// UPDATE DEPENDENCY VERSIONS HERE
const DEFAULT_DEPENDENCIES = {
Expand All @@ -24,7 +25,7 @@ const DEFAULT_DEV_DEPENDENCIES = {
module.exports = async (appPath: string, appName: string, verbose: boolean, cwd: string = '') => {
const ownPackageName: string = require('../../package.json').name;
const ownPath: string = path.join(appPath, 'node_modules', ownPackageName);
const useYarn: boolean = await pathExists(path.join(appPath, 'yarn.lock'));
const useYarn: boolean = hasYarn(appPath);
const npmOrYarn = useYarn ? 'yarn' : 'npm';

// FIXME(perry) remove when npm 5 is supported
Expand Down
3 changes: 2 additions & 1 deletion react-native-scripts/src/scripts/ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ import path from 'path';
import pathExists from 'path-exists';
import qr from 'qrcode-terminal';
import log from '../util/log';
import { hasYarn } from '../util/pm';

import packager from '../util/packager';

Config.validation.reactNativeVersionWarnings = false;
Config.developerTool = 'crna';
Config.offline = true;

const command: string = pathExists.sync(path.join(process.cwd(), 'yarn.lock')) ? 'yarnpkg' : 'npm';
const command: string = hasYarn(process.cwd()) ? 'yarnpkg' : 'npm';

if (!Simulator.isPlatformSupported()) {
log(
Expand Down
35 changes: 35 additions & 0 deletions react-native-scripts/src/util/__tests__/pm.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

jest.mock('fs');

import { hasYarn } from '../pm';

describe('hasYarn', () => {
const MOCK_FILE_INFO = {
'/a/b/yarn.lock': 'fake yarn.lock',
};

beforeEach(() => {
// Set up some mocked out file info before each test
require('fs').__setMockFiles(MOCK_FILE_INFO);
});
test('undefined path will throw exception', () => {
expect(() => hasYarn(undefined, false)).toThrow();
});
test('empty path is ok', () => {
expect(hasYarn('', false)).toEqual(false);
});
test('can find yarn in the given path', () => {
expect(hasYarn('/a/b', false)).toEqual(true);
});
test('can find yarn in the parent path', () => {
expect(hasYarn('/a/b/c/d/e', false)).toEqual(true);
});
test('can NOT find yarn in the children path', () => {
expect(hasYarn('/a', false)).toEqual(false);
});
test('can use cached value 2nd time around', () => {
expect(hasYarn('/a/b/c/d/e', false)).toEqual(true);
expect(hasYarn(undefined, true)).toEqual(true);
});
});
3 changes: 2 additions & 1 deletion react-native-scripts/src/util/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import spawn from 'cross-spawn';
import pathExists from 'path-exists';
import path from 'path';
import log from '../util/log';
import { hasYarn } from './pm';

type InstallResult = {
code: number,
Expand All @@ -17,7 +18,7 @@ export default (async function install(
packageVersion?: string,
options?: any = {}
): Promise<InstallResult> {
const useYarn: boolean = await pathExists(path.join(appPath, 'yarn.lock'));
const useYarn: boolean = hasYarn(appPath);

let command = '';
let args = [];
Expand Down
42 changes: 42 additions & 0 deletions react-native-scripts/src/util/pm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// @flow

'use strict';

import path from 'path';
import fs from 'fs';

/**
* check if the current path uses yarn, i.e. looking for yarn.lock in
* the current path and up
*
* @param {*} startingPath a path where we will look for yarn.lock file.
* Will traverse up the filesystem until we either find the file or reach the root
*
* @param {boolean} useCached if true and we have a cached hasYarn result, it will be returned, otherwise go through the
* normal lookup logic described above. mainly for optimization purpose, default is true.
*/
let _hasYarn: ?boolean;
export function hasYarn(startingPath: string, useCached: boolean = true): boolean {
if (_hasYarn != null && useCached) {
return _hasYarn;
}

_hasYarn = false;
let p = path.normalize(startingPath);
while (p.length > 0) {
const yarnLock = path.resolve(p, 'yarn.lock');
try {
const file = path.join(p, 'yarn.lock');
fs.accessSync(file);
_hasYarn = true;
break;
} catch (e) {
const parsed = path.parse(p);
if (parsed.root === parsed.dir) {
break;
}
p = parsed.dir;
}
}
return _hasYarn;
}
2 changes: 1 addition & 1 deletion react-native-scripts/taskfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default async function (fly) {
}

export async function babel(fly, opts) {
await fly.source(opts.src || paths.source).babel().target(paths.build);
await fly.source(opts.src || paths.source, {ignore: '**/__tests__/**'}).babel().target(paths.build);
}

export async function clean(fly) {
Expand Down

0 comments on commit a86f49f

Please sign in to comment.