Skip to content

Commit

Permalink
fix(lib): use deep merge strategy for context and options in Plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
xllily committed May 25, 2023
1 parent 22bfb01 commit 2a32f1d
Show file tree
Hide file tree
Showing 3 changed files with 96 additions and 2 deletions.
3 changes: 2 additions & 1 deletion lib/plugin/Plugin.js
@@ -1,5 +1,6 @@
import { debug } from 'node:util';
import _ from 'lodash';
import { deepMerge } from '../util.js';

class Plugin {
static isEnabled() {
Expand Down Expand Up @@ -40,7 +41,7 @@ class Plugin {
afterRelease() {}

getContext(path) {
const context = Object.assign({}, this.options, this.context);
const context = deepMerge({}, this.options, this.context)
return path ? _.get(context, path) : context;
}

Expand Down
48 changes: 48 additions & 0 deletions lib/util.js
Expand Up @@ -89,6 +89,53 @@ const parseVersion = raw => {
};
};


/**
* Deeply merges multiple objects into a target object, supporting deep nesting.
* Existing values in the target object are preserved when `undefined` or `null` values are encountered.
* The custom function is a simpler implementation that focuses on deep merging objects while preserving values
* But it may not handle arrays or other edge cases as extensively as Lodash's merge() function
*
* @param {object} target - The target object to merge into.
* @param {...object} sources - The source objects to merge into the target object.
* @returns {object} - The merged object.
* @throws {TypeError} - If the target is not an object or is null.
*/
const deepMerge = (target, ...sources) => {
// Check that the target is an object and not null
if (typeof target !== 'object' || target === null) {
throw new TypeError('Target must be an object');
}
let sourceValue, targetValue;
// Loop through the sources
for (const source of sources) {
// Skip if source is not an object or is null
if (typeof source !== 'object' || source === null) {
continue;
}
// Loop through each key in the source
for (const key in source) {
// Only check keys that are owned by the source object
if (source.hasOwnProperty(key)) {
sourceValue = source[key];
targetValue = target[key];
// If the source value is an object, do a deep merge
if (typeof sourceValue === 'object' && sourceValue !== null) {
if (typeof targetValue === 'object' && targetValue !== null) {
target[key] = deepMerge(Object.assign({}, targetValue), sourceValue);
} else {
target[key] = deepMerge({}, sourceValue);
}
} else if (sourceValue !== undefined && sourceValue !== null) {
// Otherwise just assign the source value to the target
target[key] = sourceValue;
}
}
}
}
return target;
};

const e = (message, docs, fail = true) => {
const error = new Error(docs ? `${message}${EOL}Documentation: ${docs}${EOL}` : message);
error.code = fail ? 1 : 0;
Expand All @@ -105,5 +152,6 @@ export {
hasAccess,
parseVersion,
readJSON,
deepMerge,
e
};
47 changes: 46 additions & 1 deletion test/utils.js
Expand Up @@ -2,7 +2,7 @@ import { EOL } from 'node:os';
import test from 'ava';
import mockStdIo from 'mock-stdio';
import stripAnsi from 'strip-ansi';
import { format, truncateLines, parseGitUrl, parseVersion } from '../lib/util.js';
import { format, truncateLines, parseGitUrl, parseVersion, deepMerge } from '../lib/util.js';

test('format', t => {
t.is(format('release v${version}', { version: '1.0.0' }), 'release v1.0.0');
Expand Down Expand Up @@ -95,3 +95,48 @@ test('parseVersion', t => {
t.deepEqual(parseVersion('1.0.0-next.1'), { version: '1.0.0-next.1', isPreRelease: true, preReleaseId: 'next' });
t.deepEqual(parseVersion('21.04.1'), { version: '21.04.1', isPreRelease: false, preReleaseId: null });
});


test("deepMerge (case 1)", t => {
const target = { a: 1, b: { c: 2, d: { e: 3 } } };
const source1 = { b: { c: 4, d: { e: null, f: 5 } }, g: 6 };
const source2 = { h: 7 };
const expected = { a: 1, b: { c: 2, d: { e: 3, f: 5 } }, g: 6, h: 7 };
const merged = deepMerge(target, source1, source2);
t.deepEqual(merged, expected);
});
test("deepMerge (case 2)", t => {
const target = { a: { b: { c: 1 } } };
const source1 = { a: { b: { d: 2 } } };
const expected = { a: { b: { c: 1, d: 2 } } };
const merged = deepMerge(target, source1);
t.deepEqual(merged, expected);
});
test("deepMerge (case 3)", t => {
const target = { a: [1, 2], b: { c: [3, 4] } };
const source1 = { a: [5, 6], b: { c: [7, 8] } };
const expected = { a: [5, 6], b: { c: [7, 8] } };
const merged = deepMerge(target, source1);
t.deepEqual(merged, expected);
});
test("deepMerge (case 4)", t => {
const target = { a: { b: { c: 1 } } };
const source1 = { a: { b: { c: undefined, d: 2 } } };
const expected = { a: { b: { c: 1, d: 2 } } };
const merged = deepMerge(target, source1);
t.deepEqual(merged, expected);
});
test("deepMerge (case 5)", t => {
const target = { a: { b: { c: 1 } } };
const source1 = undefined;
const expected = { a: { b: { c: 1 } } };
const merged = deepMerge(target, source1);
t.deepEqual(merged, expected);
});
test("deepMerge (case 6)", t => {
const target = { a: { b: { c: 1 } } };
const source1 = null;
const expected = { a: { b: { c: 1 } } };
const merged = deepMerge(target, source1);
t.deepEqual(merged, expected);
});

0 comments on commit 2a32f1d

Please sign in to comment.