Skip to content

Commit ceefe13

Browse files
committed
Fix store setter to preserve internal migration data
Fixes #114
1 parent cdd7ad4 commit ceefe13

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

source/index.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,18 @@ export default class Conf<T extends Record<string, any> = Record<string, unknown
376376
set store(value: T) {
377377
this._ensureDirectory();
378378

379+
// Preserve existing internal data if it exists and the new value doesn't contain it
380+
if (!hasProperty(value, INTERNAL_KEY)) {
381+
try {
382+
const currentStore = this.store;
383+
if (hasProperty(currentStore, INTERNAL_KEY)) {
384+
setProperty(value, INTERNAL_KEY, getProperty(currentStore, INTERNAL_KEY));
385+
}
386+
} catch {
387+
// If we can't read the current store, just proceed without preserving internal data
388+
}
389+
}
390+
379391
this._validate(value);
380392
this._write(value);
381393

test/index.ts

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1173,3 +1173,105 @@ test('beforeEachMigration - should be called before every migration', t => {
11731173
t.true(conf.get('beforeEachMigration 1.0.0 → 1.0.1'));
11741174
t.false(conf.has('beforeEachMigration 1.0.1 → 2.0.1'));
11751175
});
1176+
1177+
test('migrations - should preserve internal data when store is overwritten', t => {
1178+
const cwd = temporaryDirectory();
1179+
let migrationRunCount = 0;
1180+
1181+
// Create a config with migrations
1182+
const conf = new Conf({
1183+
cwd,
1184+
projectVersion: '2.0.0',
1185+
migrations: {
1186+
'2.0.0'(store) {
1187+
migrationRunCount++;
1188+
store.set('lastSeenVersion', '2.0.0');
1189+
},
1190+
},
1191+
});
1192+
1193+
// Migration should run once initially
1194+
t.is(migrationRunCount, 1);
1195+
t.is(conf.get('__internal__.migrations.version'), '2.0.0');
1196+
1197+
// Overwrite the store - this should NOT destroy the migration info
1198+
conf.store = {newData: 'test'};
1199+
1200+
// The internal migration version should still be preserved
1201+
t.is(conf.get('__internal__.migrations.version'), '2.0.0');
1202+
1203+
// Create a new config instance - migration should NOT run again
1204+
const conf2 = new Conf({
1205+
cwd,
1206+
projectVersion: '2.0.0',
1207+
migrations: {
1208+
'2.0.0'(store) {
1209+
migrationRunCount++;
1210+
store.set('lastSeenVersion', '2.0.0');
1211+
},
1212+
},
1213+
});
1214+
1215+
// Migration should not run again
1216+
t.is(migrationRunCount, 1);
1217+
t.is(conf2.get('__internal__.migrations.version'), '2.0.0');
1218+
});
1219+
1220+
test('migrations - should preserve internal data when store is set to empty object', t => {
1221+
const cwd = temporaryDirectory();
1222+
1223+
const conf = new Conf({
1224+
cwd,
1225+
projectVersion: '1.0.0',
1226+
migrations: {
1227+
'1.0.0'(store) {
1228+
store.set('migrated', true);
1229+
},
1230+
},
1231+
});
1232+
1233+
t.is(conf.get('__internal__.migrations.version'), '1.0.0');
1234+
1235+
// Set store to empty object
1236+
conf.store = {};
1237+
1238+
// Internal data should still be preserved
1239+
t.is(conf.get('__internal__.migrations.version'), '1.0.0');
1240+
});
1241+
1242+
test('store setter - should work when config file does not exist yet', t => {
1243+
const cwd = temporaryDirectory();
1244+
const conf = new Conf({cwd});
1245+
1246+
// Set store when no file exists
1247+
t.notThrows(() => {
1248+
conf.store = {foo: 'bar'};
1249+
});
1250+
1251+
t.is(conf.get('foo'), 'bar');
1252+
});
1253+
1254+
test('migrations - should preserve internal data without dot notation access', t => {
1255+
const cwd = temporaryDirectory();
1256+
1257+
const conf = new Conf({
1258+
cwd,
1259+
projectVersion: '1.0.0',
1260+
accessPropertiesByDotNotation: false,
1261+
migrations: {
1262+
'1.0.0'(store) {
1263+
store.set('migrated', true);
1264+
},
1265+
},
1266+
});
1267+
1268+
const internal = conf.get('__internal__') as any; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
1269+
t.is(internal.migrations.version, '1.0.0');
1270+
1271+
// Overwrite store
1272+
conf.store = {newData: 'test'};
1273+
1274+
// Internal data should still be preserved
1275+
const internal2 = conf.get('__internal__') as any; // eslint-disable-line @typescript-eslint/no-unsafe-assignment
1276+
t.is(internal2.migrations.version, '1.0.0');
1277+
});

0 commit comments

Comments
 (0)