Skip to content

Commit

Permalink
Merge pull request #143 from srowhani/feat/config-resolution
Browse files Browse the repository at this point in the history
Feat/config resolution
  • Loading branch information
srowhani authored Dec 3, 2019
2 parents 3c12e16 + 7963265 commit f5dbd33
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 103 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,16 +60,17 @@
"@sentry/node": "^5.7.0",
"chalk": "^3.0.0",
"commander": "^4.0.1",
"cosmiconfig": "^6.0.0",
"glob": "^7.1.4",
"gonzales-pe-sl": "github:srowhani/gonzales-pe#dev",
"js-yaml": "^3.13.1",
"sass-lint": "^1.13.1"
},
"devDependencies": {
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@semantic-release/git": "^7.0.8",
"@types/jest": "^24.0.23",
"@types/js-yaml": "^3.12.1",
"@types/node": "^12.12.8",
"coveralls": "^3.0.0",
"husky": "^3.1.0",
Expand Down
94 changes: 48 additions & 46 deletions src/helpers/get-config.ts
Original file line number Diff line number Diff line change
@@ -1,52 +1,54 @@
import { Nullable, SlfParserOptions } from '@src/types';
import * as fs from 'fs';
import * as path from 'path';

const merge = require('merge');
const yaml = require('js-yaml');

type OptionParser = (filename: string) => Nullable<SlfParserOptions>;
interface MappedParserOptions {
[key: string]: OptionParser;
import merge from 'merge';

import { ConfigOpts } from '@src/types';
import { cosmiconfigSync as configSync } from 'cosmiconfig';
import { readFileSync } from 'fs';
import { safeLoad } from 'js-yaml';
import { LintOpts } from 'sass-lint';

const defaultSearchPlaces = (moduleName: string) => [
'package.json',
`.${moduleName}rc`,
`.${moduleName}.json`,
`.${moduleName}.yaml`,
`.${moduleName}.yml`,
`${moduleName}.config.js`,
];

export function loadDefaults(): ConfigOpts {
return safeLoad(
readFileSync(require.resolve('../config/default.yml'), {
encoding: 'utf8',
}),
);
}

const _configurationProxy = new Proxy<MappedParserOptions>(
{
yml: parseYaml,
yaml: parseYaml,
json: parseJSON,
},
{
get(target: MappedParserOptions, filename: string) {
const resolvedParserKey = Object.keys(target).find(targetExtension =>
filename.endsWith(`.${targetExtension}`),
);

const resolvedParser =
(resolvedParserKey && target[resolvedParserKey]) || parseModule;

return resolvedParser(filename);
},
},
);

function parseYaml(filename: string): Nullable<SlfParserOptions> {
return yaml.safeLoad(fs.readFileSync(filename).toString());
export enum CONFIG_TYPE {
SASS_LINT = 'sass_lint',
SASS_LINT_AUTO_FIX = 'sass_lint_auto_fix',
}

function parseJSON(filename: string): Nullable<SlfParserOptions> {
const file = fs.readFileSync(filename).toString();
return JSON.parse(file);
export type ConfigType<T> = T extends CONFIG_TYPE.SASS_LINT
? LintOpts
: T extends CONFIG_TYPE.SASS_LINT_AUTO_FIX
? ConfigOpts
: any;

export function getConfig<T extends CONFIG_TYPE>(
moduleName: T,
filepath?: string,
): ConfigType<T> {
const explorer = configSync(moduleName, {
searchPlaces: defaultSearchPlaces(moduleName),
});

const resolvedConfig = filepath ? explorer.load(filepath) : explorer.search();

if (resolvedConfig) {
return resolvedConfig.config;
}
return {} as any;
}

function parseModule(filename: string): Nullable<SlfParserOptions> {
return require(path.resolve(filename));
}

export const getConfig = (filename: string): Record<string, any> =>
_configurationProxy[filename];

export const mergeConfig = (
baseConfig: Record<string, any>,
extendedConfig: Record<string, any>,
) => merge.recursive(true, baseConfig, extendedConfig);
export const mergeConfig = <A, B>(baseConfig: A, extendedConfig: B) =>
merge.recursive(true, baseConfig, extendedConfig);
46 changes: 25 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
#!/usr/bin/env node
import { createLogger, getConfig, mergeConfig } from './helpers';
import {
CONFIG_TYPE,
createLogger,
getConfig,
loadDefaults,
mergeConfig,
} from './helpers';

import { LintOpts } from 'sass-lint';
import program from 'commander';
import { autoFixSassFactory } from './sass-lint-auto-fix';
import { SentryService } from './services';
import { ConfigOpts } from './types';

const process = require('process');
const program = require('commander');
const fs = require('fs');
import fs from 'fs';
import process from 'process';

const { version } = require('../package.json');

Expand All @@ -33,30 +37,30 @@ const { version } = require('../package.json');
debugEnabled: program.debug,
});

const config = getConfig(require.resolve('./config/default.yml'));
let slConfig: Partial<LintOpts> = {};

let defaultOptions = { ...config } as ConfigOpts;
let baseOptions = loadDefaults();
if (program.config) {
// TOOD: Handle different configuration types
const customConfiguration = getConfig(program.config);
defaultOptions = mergeConfig(defaultOptions, customConfiguration);
const customConfiguration = getConfig(
CONFIG_TYPE.SASS_LINT_AUTO_FIX,
program.config,
);
baseOptions = mergeConfig(baseOptions, customConfiguration);
}

// Pass in custom sass-lint configuration
if (program.configSassLint) {
slConfig = getConfig(program.configSassLint);
}
const sassLintConfig = getConfig(
CONFIG_TYPE.SASS_LINT,
program.configSassLint,
);

process.on('unhandledRejection', (error: Error) => {
if (!defaultOptions.options.optOut) {
if (!baseOptions.options.optOut) {
SentryService.reportIncident(error);
}
logger.error(error);
});

process.on('uncaughtException', (error: Error) => {
if (!defaultOptions.options.optOut) {
if (!baseOptions.options.optOut) {
SentryService.reportIncident(error);
}
logger.error(error);
Expand All @@ -65,15 +69,15 @@ const { version } = require('../package.json');

const pattern = program.args[0];

defaultOptions.files.include = pattern || defaultOptions.files.include;
baseOptions.files.include = pattern || baseOptions.files.include;

const sassLintAutoFix = autoFixSassFactory({
logger,
...defaultOptions,
...baseOptions,
});

// TODO: Add sass-lint config, right now will merge with default rule set
for (const { filename, ast } of sassLintAutoFix(slConfig as LintOpts)) {
for (const { filename, ast } of sassLintAutoFix(sassLintConfig)) {
fs.writeFileSync(filename, ast.toString());
logger.verbose('write', `Writing resolved tree to ${filename}`);
}
Expand Down
3 changes: 3 additions & 0 deletions src/types/merge.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module 'merge' {
export function recursive<A, B>(clone: boolean, a: A, b: B): A & B;
}
2 changes: 1 addition & 1 deletion test/sample-config/config
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module.exports = {
{
resolvers: {
'property-sort-order': 1
}
Expand Down
5 changes: 5 additions & 0 deletions test/sample-config/configrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
resolvers: {
'property-sort-order': 1
}
}
7 changes: 7 additions & 0 deletions test/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import { exec, maybeBuild } from '@test/helpers/cmd';
import { version } from '../../package.json';

const CLI_TEST_TIMEOUT = 10_000;

describe('cli', () => {
beforeAll(maybeBuild);

it('returns correct version', async () => {
const versionResult = await exec('node dist/index.js -V');
expect(versionResult).toContain(version);
});

it(
'prints help dialog with -h flag',
async () => {
Expand Down
53 changes: 21 additions & 32 deletions test/src/helpers/get-config.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,28 @@
import { getConfig, mergeConfig } from '@src/helpers/get-config';
describe('get-config', () => {
describe('getConfig', () => {
it('[handles=yml]', () => {
const config = getConfig('test/sample-config/config.yml');
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
});
import { CONFIG_TYPE, getConfig, mergeConfig } from '@src/helpers/get-config';

it('[handles=yaml]', () => {
const config = getConfig('test/sample-config/config.yaml');
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
});
function slafConfig(configPath: string) {
return getConfig(CONFIG_TYPE.SASS_LINT_AUTO_FIX, configPath);
}

it('[handles=js]', () => {
const config = getConfig('test/sample-config/config.js');
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
});

it('[handles=ts]', () => {
const config = getConfig('test/sample-config/config.ts');
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
});

it('[handles=*proxy]', () => {
const config = getConfig('test/sample-config/config');
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
});
function assertConfigLoads(configPath: string) {
const config = slafConfig(configPath);
expect(typeof config).toBe('object');
expect(config.resolvers['property-sort-order']).toBe(1);
}

describe('get-config', () => {
describe('getConfig', () => {
it('[handles=yml]', () =>
assertConfigLoads('test/sample-config/config.yml'));
it('[handles=yaml]', () =>
assertConfigLoads('test/sample-config/config.yaml'));
it('[handles=js]', () => assertConfigLoads('test/sample-config/config.js'));
it('[handles=rc]', () => assertConfigLoads('test/sample-config/configrc'));
it('[handles=no ext]', () =>
assertConfigLoads('test/sample-config/config'));
it('[handles=bad export]', () => {
expect(() => getConfig('test/sample-config/doesnt.exist')).toThrowError(
/Cannot find module/,
expect(() => slafConfig('test/sample-config/doesnt.exist')).toThrowError(
/no such file or directory/,
);
});
});
Expand Down
5 changes: 3 additions & 2 deletions test/src/sass-lint-auto-fix.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createLogger, getConfig } from '@src/helpers';
import { CONFIG_TYPE, createLogger, getConfig } from '@src/helpers';
import { autoFixSassFactory } from '@src/sass-lint-auto-fix';
import { ConfigOpts, ValidFileType } from '@src/types';
import { LintOpts } from 'sass-lint';
Expand Down Expand Up @@ -116,8 +116,9 @@ describe('sass-lint-auto-fix', () => {
const slaf = autoFixSassFactory(configOpts);

const customSlConfig = getConfig(
CONFIG_TYPE.SASS_LINT,
'test/config/custom-sl-config.yml',
) as LintOpts;
);

expect(customSlConfig).toEqual({
options: {
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,11 @@
dependencies:
jest-diff "^24.3.0"

"@types/js-yaml@^3.12.1":
version "3.12.1"
resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.1.tgz#5c6f4a1eabca84792fbd916f0cb40847f123c656"
integrity sha512-SGGAhXLHDx+PK4YLNcNGa6goPf9XRWQNAUUbffkwVGGXIxmDKWyGGL4inzq2sPmExu431Ekb9aEMn9BkPqEYFA==

"@types/minimatch@*", "@types/minimatch@3.0.3":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
Expand Down

0 comments on commit f5dbd33

Please sign in to comment.