Skip to content

Commit

Permalink
FEATURE: Expose a resolveConfigPath method which will return the full…
Browse files Browse the repository at this point in the history
… path to the resolved config up the tree
  • Loading branch information
Inkdpixels committed Nov 21, 2017
1 parent f6ed610 commit fcbc919
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 64 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"semantic-release pre && npm publish && semantic-release post"
},
"lint-staged": {
"**/*.{js,json,css}": ["prettier --write", "git add"]
"src/**/*.{js,json,css}": ["prettier --write", "git add"]
},
"dependencies": {
"find-up": "^2.1.0",
Expand Down
60 changes: 29 additions & 31 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,8 @@ type OptionsType = {
cwd?: string
};

const fs = require('fs');
const {promisify} = require('util');
const merge = require('lodash/merge');
const findUp = require('find-up');

const _utils = {
readFileAsync: promisify(fs.readFile),
findUp
};
const utils = require('./utils.js');

async function findConfigUp(options: OptionsType): Promise<any> {
const {
Expand All @@ -27,45 +20,50 @@ async function findConfigUp(options: OptionsType): Promise<any> {
let config;

if (rawConfigFileName && rawConfigFileName.length) {
const rawConfigPath = await _utils.findUp(rawConfigFileName, {cwd});
const rawConfigPath = await utils.findUp(rawConfigFileName, {cwd});

if (rawConfigPath && rawConfigPath.length) {
config = await readJson(rawConfigPath);
config = await utils.readJson(rawConfigPath);
}
}

if (!config) {
config = await readPackageJsonUp(packageJsonProperty, cwd);
const configPath = await utils.resolvePackageJsonConfigPath(
packageJsonProperty,
cwd
);

if (configPath && configPath.length) {
const packageJson = await utils.readJson(configPath);

config = packageJson[packageJsonProperty];
}
}

return merge({}, defaults, config);
}
findConfigUp._utils = _utils;

async function readPackageJsonUp(propertyName: string, cwd: string) {
const filePath = await _utils.findUp('package.json', {cwd});
async function resolveConfigPath(options: {
cwd?: string,
rawConfigFileName?: string,
packageJsonProperty: string
}): Promise<string | void> {
const {cwd = process.cwd(), rawConfigFileName, packageJsonProperty} = options;
let configPath;

if (!filePath) {
return undefined;
if (rawConfigFileName && rawConfigFileName.length) {
configPath = await utils.findUp(rawConfigFileName, {cwd});
}

const pkg = await readJson(filePath);

const config = pkg[propertyName];

if (typeof config === 'object') {
return config;
if (!configPath) {
configPath = await utils.resolvePackageJsonConfigPath(
packageJsonProperty,
cwd
);
}

const pathPartials = cwd.split('/');
pathPartials.pop();

return readPackageJsonUp(propertyName, pathPartials.join('/'));
}
async function readJson(path: string | any): Promise<Object> {
const contents = await _utils.readFileAsync(path, 'utf8');

return JSON.parse(contents);
return configPath;
}

module.exports = findConfigUp;
module.exports.resolveConfigPath = resolveConfigPath;
73 changes: 41 additions & 32 deletions src/index.spec.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,24 @@
// @flow

const findConfigUp = require('./index.js');

const mockFs = {
'/foo/raw-config/nested/folder/some.json': '{}',
'/foo/raw-config/nested/package.json': '{}',
'/foo/raw-config/.foorc': '{"someRawRcProperty": true}',
jest.mock('./utils.js');

'/foo/package-json/nested/folder/package.json': '{}',
'/foo/package-json/nested/package.json': '{}',
'/foo/package-json/package.json': '{"foo": {"somePackageJsonProperty": true}}'
};
const utils: any = require('./utils.js');
const findConfigUp = require('./index.js');

describe('findConfigUp()', () => {
let readFileAsync;
let findUp;

beforeEach(() => {
readFileAsync = jest
.spyOn(findConfigUp._utils, 'readFileAsync')
.mockImplementation(jest.fn(path => mockFs[path]));
findUp = jest
.spyOn(findConfigUp._utils, 'findUp')
.mockImplementation(jest.fn());
});

afterEach(() => {
readFileAsync.mockRestore();
findUp.mockRestore();
jest.resetAllMocks();
});

it('should be a function', () => {
expect(typeof findConfigUp).toBe('function');
});

it('should return the contents of the provided "rawConfigFileName" filename if one was found up the filesystem tree', async () => {
findUp.mockReturnValueOnce('/foo/raw-config/.foorc');
utils.findUp.mockReturnValueOnce('/foo/raw-config/.foorc');
utils.readJson.mockReturnValueOnce({
someRawRcProperty: true
});

const cfg = await findConfigUp({
rawConfigFileName: '.foorc',
Expand All @@ -48,11 +31,15 @@ describe('findConfigUp()', () => {
});

it('should return the contents of the "package.json" with the given "packageJsonProperty" property if one was found up the filesystem tree', async () => {
findUp
.mockReturnValueOnce(null)
.mockReturnValueOnce('/foo/package-json/nested/folder/package.json')
.mockReturnValueOnce('/foo/package-json/nested/package.json')
.mockReturnValue('/foo/package-json/package.json');
utils.findUp.mockReturnValue(null);
utils.resolvePackageJsonConfigPath.mockReturnValue(
'/foo/package-json/package.json'
);
utils.readJson.mockReturnValue({
foo: {
somePackageJsonProperty: true
}
});

const cfg = await findConfigUp({
rawConfigFileName: '.foorc',
Expand All @@ -65,7 +52,7 @@ describe('findConfigUp()', () => {
});

it('should return the defaults if no file was found', async () => {
findUp.mockReturnValue(null);
utils.findUp.mockReturnValue(null);

const cfg = await findConfigUp({
packageJsonProperty: 'foo',
Expand All @@ -77,7 +64,7 @@ describe('findConfigUp()', () => {
});

it('should not crash if no cwd was given', () => {
findUp.mockReturnValue(null);
utils.findUp.mockReturnValue(null);

expect(() =>
findConfigUp({
Expand All @@ -87,3 +74,25 @@ describe('findConfigUp()', () => {
).not.toThrow();
});
});

describe('findConfigUp.resolveConfigPath()', () => {
afterEach(() => {
jest.resetAllMocks();
});

it('should be a function', () => {
expect(typeof findConfigUp.resolveConfigPath).toBe('function');
});

it('should try to resolve the raw config path and if not found fall back to resolving the package.json path', async () => {
utils.findUp.mockReturnValue(null);
utils.resolvePackageJsonConfigPath.mockReturnValue('/foo/bar/package.json');

const path = await findConfigUp.resolveConfigPath({
rawConfigFileName: '.foorc',
packageJsonProperty: 'foo'
});

expect(path).toBe('/foo/bar/package.json');
});
});
41 changes: 41 additions & 0 deletions src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// @flow

const fs = require('fs');
const {promisify} = require('util');
const findUp = require('find-up');

module.exports = {
readFileAsync: promisify(fs.readFile),
findUp,

async resolvePackageJsonConfigPath(
propertyName: string,
cwd: string
): Promise<string | void> {
const filePath = await this.findUp('package.json', {cwd});

if (!filePath) {
return;
}

const pkg = await this.readJson(filePath);

if (typeof pkg[propertyName] === 'object') {
return filePath;
}

const pathPartials = cwd.split('/');
pathPartials.pop();

return this.resolvePackageJsonConfigPath(
propertyName,
pathPartials.join('/')
);
},

async readJson(path: string | any): Promise<Object> {
const contents = await this.readFileAsync(path, 'utf8');

return JSON.parse(contents);
}
};
88 changes: 88 additions & 0 deletions src/utils.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// @flow

const utils = require('./utils.js');

describe('utils.findUp()', () => {
it('should be a function', () => {
expect(typeof utils.findUp).toBe('function');
});
});

describe('utils.readFileAsync()', () => {
it('should be a function', () => {
expect(typeof utils.readFileAsync).toBe('function');
});
});

describe('utils.resolvePackageJsonConfigPath()', () => {
let findUp;
let readJson;

beforeEach(() => {
findUp = jest.spyOn(utils, 'findUp').mockImplementation(jest.fn());
readJson = jest.spyOn(utils, 'readJson').mockImplementation(jest.fn());
});

afterEach(() => {
findUp.mockRestore();
readJson.mockRestore();
});

it('should be a function', () => {
expect(typeof utils.resolvePackageJsonConfigPath).toBe('function');
});

it('should resolve a path to a package.json file up the tree with the given property name', async () => {
findUp
.mockReturnValueOnce('/foo/bar/baz/package.json')
.mockReturnValueOnce('/foo/bar/package.json')
.mockReturnValueOnce('/foo/package.json');
readJson
.mockReturnValueOnce({})
.mockReturnValueOnce({})
.mockReturnValueOnce({foo: {}});

const path = await utils.resolvePackageJsonConfigPath(
'foo',
'/foo/bar/baz'
);

expect(path).toEqual('/foo/package.json');
});

it('should return undefined if no package.json was found up the tree', async () => {
findUp.mockReturnValue(null);

const path = await utils.resolvePackageJsonConfigPath(
'foo',
'/foo/bar/baz'
);

expect(path).toBe(undefined);
});
});

describe('utils.readJson()', () => {
let readFileAsync;

beforeEach(() => {
readFileAsync = jest
.spyOn(utils, 'readFileAsync')
.mockImplementation(jest.fn());
});

afterEach(() => {
readFileAsync.mockRestore();
});

it('should be a function', () => {
expect(typeof utils.readJson).toBe('function');
});

it('should call the readFileAsync method and parse the return value as JSON', async () => {
readFileAsync.mockReturnValue('{"foo": "bar"}');
const contents = await utils.readJson('/foo/bar.json');

expect(contents).toEqual({foo: 'bar'});
});
});

0 comments on commit fcbc919

Please sign in to comment.