Skip to content

Commit 833fcba

Browse files
authored
fix: preserve base env when optional override fails and reset init state on clean() (#143)
1 parent 3c782c9 commit 833fcba

4 files changed

Lines changed: 88 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,17 @@ Release notes are available on [github][notes].
1212

1313
- [fix] Replace `assert()` with explicit `if`/`throw` in `getInt()`, `getDouble()`, and `getBool()` so null-safety checks are enforced in release builds
1414
- Error messages now include the variable name for easier debugging
15+
- [fix] `load(isOptional: true)` no longer discards successfully loaded base file when an override file is missing or empty (fixes #70, #93, #101, #125)
16+
- [fix] `clean()` now resets `isInitialized` to `false`, so accessing `env` after `clean()` correctly throws `NotInitializedError`
1517

1618
### Note on release-build behavior change
1719
In **debug mode**, behavior is unchanged — `AssertionError` was thrown before, `AssertionError` is thrown now.
1820

1921
In **release mode**, calling `getInt()`, `getDouble()`, or `getBool()` with a missing variable and no fallback previously threw a `TypeError` (from the null-check operator `!`) because `assert()` was stripped. It now correctly throws `AssertionError` with a descriptive message. If your release-mode code catches `on TypeError` around these methods, update it to catch `on AssertionError` (or `on Error`) instead.
2022

23+
### Breaking change: `clean()` now resets initialization state
24+
Previously, calling `clean()` only cleared the env map but left `isInitialized == true`. Now it also sets `isInitialized = false`. Code that calls `clean()` and then immediately accesses `dotenv.env` without reloading will now throw `NotInitializedError`. The fix is to call `load()` or `loadFromString()` again after `clean()`.
25+
2126
# 6.0.0
2227

2328
- [feat] Allow passing in override .env files on init

lib/src/dotenv.dart

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,11 @@ class DotEnv {
4545

4646
bool get isInitialized => _isInitialized;
4747

48-
/// Clear [env]
49-
void clean() => _envMap.clear();
48+
/// Clear [env] and reset initialization state
49+
void clean() {
50+
_envMap.clear();
51+
_isInitialized = false;
52+
}
5053

5154
String get(String name, {String? fallback}) {
5255
final value = maybeGet(name, fallback: fallback);
@@ -134,19 +137,26 @@ class DotEnv {
134137
Parser parser = const Parser(),
135138
}) async {
136139
clean();
137-
List<String> linesFromFile;
138-
List<String> linesFromOverrides;
140+
List<String> linesFromFile = [];
141+
List<String> linesFromOverrides = [];
142+
139143
try {
140144
linesFromFile = await _getEntriesFromFile(fileName);
141-
linesFromOverrides = await _getLinesFromOverride(overrideWithFiles);
142145
} on FileNotFoundError {
143146
if (!isOptional) rethrow;
144-
linesFromFile = [];
145-
linesFromOverrides = [];
146147
} on EmptyEnvFileError {
147148
if (!isOptional) rethrow;
148-
linesFromFile = [];
149-
linesFromOverrides = [];
149+
}
150+
151+
for (final overrideFile in overrideWithFiles) {
152+
try {
153+
final lines = await _getEntriesFromFile(overrideFile);
154+
linesFromOverrides.addAll(lines);
155+
} on FileNotFoundError {
156+
if (!isOptional) rethrow;
157+
} on EmptyEnvFileError {
158+
if (!isOptional) rethrow;
159+
}
150160
}
151161

152162
final linesFromMergeWith = mergeWith.entries
@@ -214,15 +224,4 @@ class DotEnv {
214224
}
215225
}
216226

217-
Future<List<String>> _getLinesFromOverride(List<String> overrideWith) async {
218-
List<String> overrideLines = [];
219-
220-
for (int i = 0; i < overrideWith.length; i++) {
221-
final overrideWithFile = overrideWith[i];
222-
final lines = await _getEntriesFromFile(overrideWithFile);
223-
overrideLines = overrideLines..addAll(lines);
224-
}
225-
226-
return overrideLines;
227-
}
228227
}

test/dotenv_test.dart

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,4 +150,66 @@ void main() {
150150
e.message.toString().contains('MISSING_VAR'))));
151151
});
152152
});
153+
154+
group('clean() resets initialization state', () {
155+
test('after clean(), isInitialized is false', () {
156+
dotenv.loadFromString(envString: 'FOO=bar');
157+
expect(dotenv.isInitialized, isTrue);
158+
dotenv.clean();
159+
expect(dotenv.isInitialized, isFalse);
160+
});
161+
162+
test('after clean(), accessing env throws NotInitializedError', () {
163+
dotenv.loadFromString(envString: 'FOO=bar');
164+
expect(dotenv.env['FOO'], 'bar');
165+
dotenv.clean();
166+
expect(() => dotenv.env, throwsA(isA<NotInitializedError>()));
167+
});
168+
169+
test(
170+
'loadFromString after clean() re-initializes correctly', () {
171+
dotenv.loadFromString(envString: 'FOO=bar');
172+
dotenv.clean();
173+
dotenv.loadFromString(envString: 'BAZ=qux');
174+
expect(dotenv.isInitialized, isTrue);
175+
expect(dotenv.env['BAZ'], 'qux');
176+
expect(dotenv.env['FOO'], isNull);
177+
});
178+
});
179+
180+
group('isOptional override loading', () {
181+
test(
182+
'loadFromString with valid base and empty override preserves base vars',
183+
() {
184+
dotenv.loadFromString(
185+
envString: 'FOO=bar\nBAZ=qux',
186+
overrideWith: [''],
187+
isOptional: true,
188+
);
189+
expect(dotenv.env['FOO'], 'bar');
190+
expect(dotenv.env['BAZ'], 'qux');
191+
});
192+
193+
test('loadFromString with empty base and valid override preserves override',
194+
() {
195+
dotenv.loadFromString(
196+
envString: '',
197+
overrideWith: ['OVERRIDE_KEY=override_value'],
198+
isOptional: true,
199+
);
200+
expect(dotenv.env['OVERRIDE_KEY'], 'override_value');
201+
});
202+
203+
test(
204+
'loadFromString with isOptional=false and empty base throws even with valid override',
205+
() {
206+
expect(
207+
() => dotenv.loadFromString(
208+
envString: '',
209+
overrideWith: ['OVERRIDE_KEY=override_value'],
210+
isOptional: false,
211+
),
212+
throwsA(isA<EmptyEnvFileError>()));
213+
});
214+
});
153215
}

test/empty_env_test.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ void main() {
3030
}, throwsA(isA<EmptyEnvFileError>()));
3131
});
3232

33-
test('missing .env file with isOptional=true should not throw', () {
33+
test('empty string with isOptional=true should not throw', () {
3434
File emptyFile = File('test/.env.empty');
3535
expect(() {
3636
dotenv.loadFromString(
@@ -41,8 +41,7 @@ void main() {
4141
expect(dotenv.env.isEmpty, isTrue);
4242
});
4343

44-
test(
45-
'missing .env file with isOptional=false should throw FileNotFoundError',
44+
test('empty string with isOptional=false should throw EmptyEnvFileError',
4645
() {
4746
expect(() {
4847
dotenv.loadFromString(envString: '', isOptional: false);

0 commit comments

Comments
 (0)