From e5a5a75938098704340f8ced7e4aaa728eca82eb Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 26 May 2026 12:22:02 -0400 Subject: [PATCH] fix(#321): replace JSON config default clone with structuredClone (#324) --- .changeset/eager-elks-romp.md | 5 ++++ get-shit-done/bin/lib/configuration.cjs | 2 +- ...21-config-defaults-clone-strategy.test.cjs | 23 +++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 .changeset/eager-elks-romp.md create mode 100644 tests/bug-321-config-defaults-clone-strategy.test.cjs diff --git a/.changeset/eager-elks-romp.md b/.changeset/eager-elks-romp.md new file mode 100644 index 000000000..aaa671249 --- /dev/null +++ b/.changeset/eager-elks-romp.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 321 +--- +**`loadConfig` now clones config defaults with `structuredClone`** — avoids JSON round-trip fragility in defaults merging used by config loads and related tooling. diff --git a/get-shit-done/bin/lib/configuration.cjs b/get-shit-done/bin/lib/configuration.cjs index e6a2bcea0..19b730e41 100644 --- a/get-shit-done/bin/lib/configuration.cjs +++ b/get-shit-done/bin/lib/configuration.cjs @@ -158,7 +158,7 @@ function normalizeLegacyKeys(parsed) { function mergeDefaults(parsed) { // Start with a deep clone of defaults, then overlay parsed - const defaults = JSON.parse(JSON.stringify(CONFIG_DEFAULTS)); + const defaults = structuredClone(CONFIG_DEFAULTS); return deepMergeConfig(defaults, parsed); } diff --git a/tests/bug-321-config-defaults-clone-strategy.test.cjs b/tests/bug-321-config-defaults-clone-strategy.test.cjs new file mode 100644 index 000000000..d0d35bc30 --- /dev/null +++ b/tests/bug-321-config-defaults-clone-strategy.test.cjs @@ -0,0 +1,23 @@ +'use strict'; + +const { test } = require('node:test'); +const assert = require('node:assert/strict'); +const configuration = require('../get-shit-done/bin/lib/configuration.cjs'); + +test('mergeDefaults clones defaults without JSON serialization fragility (#321)', () => { + const sentinelKey = '__bug321_bigint_sentinel__'; + const sentinelValue = BigInt('9007199254740993001'); + + configuration.CONFIG_DEFAULTS[sentinelKey] = sentinelValue; + try { + const merged = configuration.mergeDefaults({}); + assert.equal( + merged[sentinelKey], + sentinelValue, + 'mergeDefaults must preserve non-JSON scalar defaults when cloning' + ); + } + finally { + delete configuration.CONFIG_DEFAULTS[sentinelKey]; + } +});