Skip to content

Commit

Permalink
✨ Prefer kebab-case for yaml config files (#167)
Browse files Browse the repository at this point in the history
* ✨ Allow normalize to accept options parameter as second arg

Overrides can be nested within options since it is only used in one place

* ✨ Allow normalizing options as kebab-cased

* ✨ Prefer kebab-case for yaml migrations
  • Loading branch information
Wil Wilsman authored Feb 5, 2021
1 parent 37f2b0b commit 6244006
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 13 deletions.
7 changes: 7 additions & 0 deletions packages/cli-config/src/commands/config/migrate.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ export class Migrate extends Command {
this.log.info('Migrating config file...');
let format = path.extname(output).replace(/^./, '') || 'yaml';
let migrated = PercyConfig.migrate(config);

// prefer kebab-case for yaml
if (/^ya?ml$/.test(format)) {
migrated = PercyConfig.normalize(migrated, { kebab: true });
}

// stringify to the desired format
let body = PercyConfig.stringify(format, migrated);

// update the package.json entry via string replacement
Expand Down
2 changes: 1 addition & 1 deletion packages/config/src/load.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export default function load({
}

// merge found config with overrides and validate
config = normalize(config, overrides);
config = normalize(config, { overrides });
let validation = config && validate(config);

if (validation && !validation.result) {
Expand Down
38 changes: 28 additions & 10 deletions packages/config/src/normalize.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,56 @@ const { isArray } = Array;
const { entries, assign } = Object;

// Edge case camelizations
const CAMELIZE_MAP = {
css: 'CSS',
javascript: 'JavaScript'
};
const CAMELCASE_MAP = new Map([
['css', 'CSS'],
['javascript', 'JavaScript']
]);

// Converts kebab-cased and snake_cased strings to camelCase.
const KEBAB_SNAKE_REG = /[-_]([^-_]+)/g;

function camelize(str) {
function camelcase(str) {
return str.replace(KEBAB_SNAKE_REG, (match, word) => (
CAMELIZE_MAP[word] || (word[0].toUpperCase() + word.slice(1))
CAMELCASE_MAP.get(word) || (word[0].toUpperCase() + word.slice(1))
));
}

// Coverts camelCased and snake_cased strings to kebab-case.
const CAMEL_SNAKE_REG = /([a-z])([A-Z]+)|_([^_]+)/g;

function kebabcase(str) {
return Array.from(CAMELCASE_MAP)
.reduce((str, [word, camel]) => (
str.replace(camel, `-${word}`)
), str)
.replace(CAMEL_SNAKE_REG, (match, p, n, w) => (
`${p || ''}-${(n || w).toLowerCase()}`
));
}

// Merges source values into the target object unless empty. When `options.replaceArrays` is truthy,
// target arrays are replaced by their source arrays rather than concatenated together.
export function merge(target, source, options) {
let isSourceArray = isArray(source);
if (options?.replaceArrays && isSourceArray) return source;
if (typeof source !== 'object') return source != null ? source : target;
let convertcase = options?.kebab ? kebabcase : camelcase;

return entries(source).reduce((result, [key, value]) => {
value = merge(result?.[key], value, options);

return value == null ? result
: isSourceArray ? (result || []).concat(value)
: assign(result || {}, { [camelize(key)]: value });
: assign(result || {}, { [convertcase(key)]: value });
}, target);
}

// Recursively reduces config objects and arrays to remove undefined and empty values and rename
// kebab-case properties to camelCase. Optionally allows deep merging of override values
export default function normalize(object, overrides) {
return merge(merge(undefined, object), overrides);
// kebab-case properties to camelCase. Optionally allows deep merging of a second overrides
// argument, and converting keys to kebab-case with a third options.kebab argument.
export default function normalize(object, options) {
object = merge(undefined, object, options);
return options?.overrides
? merge(object, options.overrides, options)
: object;
}
24 changes: 22 additions & 2 deletions packages/config/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -511,11 +511,31 @@ describe('PercyConfig', () => {
expect(PercyConfig.normalize({
'foo-bar': 'baz',
foo: { bar_baz: 'qux' },
'foo_bar-baz': 'qux'
'foo_bar-baz': 'qux',
'percy-css': '',
'enable-javascript': false
})).toEqual({
fooBar: 'baz',
foo: { barBaz: 'qux' },
fooBarBaz: 'qux'
fooBarBaz: 'qux',
percyCSS: '',
enableJavaScript: false
});
});

it('can converts keys to kebab-case', () => {
expect(PercyConfig.normalize({
'foo-bar': 'baz',
foo: { bar_baz: 'qux' },
fooBar_baz: 'qux',
percyCSS: '',
enableJavaScript: false
}, { kebab: true })).toEqual({
'foo-bar': 'baz',
foo: { 'bar-baz': 'qux' },
'foo-bar-baz': 'qux',
'percy-css': '',
'enable-javascript': false
});
});
});
Expand Down

0 comments on commit 6244006

Please sign in to comment.