From e7872e1ac40c9cf98bac9943bb51907a7d34bb40 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 7 Jun 2026 11:08:21 +0100 Subject: [PATCH 1/3] chore: update build_test dependency version in pubspec.yaml --- packages/envied_generator/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/envied_generator/pubspec.yaml b/packages/envied_generator/pubspec.yaml index c483005..36edfc2 100644 --- a/packages/envied_generator/pubspec.yaml +++ b/packages/envied_generator/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: source_gen: ^4.1.1 dev_dependencies: + build_test: ^3.5.15 lints: ^6.0.0 source_gen_test: ^1.3.3 test: ^1.28.0 From e83e24b7aed69946474ab127dfecf35a44dc0392 Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 7 Jun 2026 11:08:35 +0100 Subject: [PATCH 2/3] feat: enhance loadEnvs and _loadEnv functions to support BuildStep for improved asset handling --- .../envied_generator/lib/src/generator.dart | 1 + .../envied_generator/lib/src/load_envs.dart | 45 ++++++++++++++++--- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/packages/envied_generator/lib/src/generator.dart b/packages/envied_generator/lib/src/generator.dart index 0e049b5..3948166 100644 --- a/packages/envied_generator/lib/src/generator.dart +++ b/packages/envied_generator/lib/src/generator.dart @@ -112,6 +112,7 @@ final class EnviedGenerator extends GeneratorForAnnotation { throw InvalidGenerationSourceError(error, element: element); } }, + buildStep: buildStep, ); final DartEmitter emitter = DartEmitter(useNullSafetySyntax: true); diff --git a/packages/envied_generator/lib/src/load_envs.dart b/packages/envied_generator/lib/src/load_envs.dart index a468be3..ddc8a34 100644 --- a/packages/envied_generator/lib/src/load_envs.dart +++ b/packages/envied_generator/lib/src/load_envs.dart @@ -1,5 +1,7 @@ +import 'dart:convert' show LineSplitter; import 'dart:io' show File; +import 'package:build/build.dart'; import 'package:envied_generator/src/env_val.dart'; import 'package:envied_generator/src/parser.dart'; @@ -10,8 +12,9 @@ import 'package:envied_generator/src/parser.dart'; /// [onError] function. Future> loadEnvs( String path, - void Function(String) onError, -) => loadEnvsFromPaths([path], onError); + void Function(String) onError, { + BuildStep? buildStep, +}) => loadEnvsFromPaths([path], onError, buildStep: buildStep); /// Load and merge environment variables from [paths]. /// @@ -19,12 +22,13 @@ Future> loadEnvs( /// duplicate keys within a single file keep the first parsed value. Future> loadEnvsFromPaths( Iterable paths, - void Function(String) onError, -) async { + void Function(String) onError, { + BuildStep? buildStep, +}) async { final Map envs = {}; for (final String path in paths) { - envs.addAll(await _loadEnv(path, onError, env: envs)); + envs.addAll(await _loadEnv(path, onError, env: envs, buildStep: buildStep)); } return envs; @@ -34,7 +38,23 @@ Future> _loadEnv( String path, void Function(String) onError, { required Map env, + BuildStep? buildStep, }) async { + final AssetId? assetId = _assetIdForPath(path, buildStep); + + if (assetId != null) { + final List lines = []; + if (await buildStep!.canRead(assetId)) { + lines.addAll( + const LineSplitter().convert(await buildStep.readAsString(assetId)), + ); + } else { + onError("Environment variable file doesn't exist at `$path`."); + } + + return Parser.parse(lines, env: env); + } + final File file = File.fromUri(Uri.file(path)); final List lines = []; @@ -46,3 +66,18 @@ Future> _loadEnv( return Parser.parse(lines, env: env); } + +AssetId? _assetIdForPath(String path, BuildStep? buildStep) { + if (buildStep == null || File(path).isAbsolute) { + return null; + } + + try { + return AssetId(buildStep.inputId.package, path); + } on ArgumentError { + return null; + } on NoSuchMethodError { + // source_gen_test passes a mock BuildStep that does not expose inputId. + return null; + } +} From 35ec123c15ead0f3eb3904ddae254ccb369b5d1c Mon Sep 17 00:00:00 2001 From: Klemen Tusar Date: Sun, 7 Jun 2026 11:08:39 +0100 Subject: [PATCH 3/3] test: add tests for loading default and inherited env files in envied_generator --- .../test/workspace_resolution_test.dart | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 packages/envied_generator/test/workspace_resolution_test.dart diff --git a/packages/envied_generator/test/workspace_resolution_test.dart b/packages/envied_generator/test/workspace_resolution_test.dart new file mode 100644 index 0000000..f151ead --- /dev/null +++ b/packages/envied_generator/test/workspace_resolution_test.dart @@ -0,0 +1,94 @@ +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:envied_generator/builder.dart'; +import 'package:test/test.dart'; + +void main() { + test('loads the default env file from the input package', () async { + final AssetId input = AssetId('envied_generator', 'lib/default_env.dart'); + final AssetId env = AssetId('envied_generator', '.env'); + + final TestBuilderResult result = await _build(input, { + '$input': ''' +import 'package:envied/envied.dart'; + +@Envied(requireEnvFile: true) +abstract class DefaultEnv { + @EnviedField() + static const String? apiUrl = null; +} +''', + '$env': 'apiUrl=https://package.example', + }); + + expect(result.succeeded, isTrue); + expect(result.readerWriter.testing.inputsTracked, contains(env)); + expect( + result.readerWriter.testing.readString( + input.changeExtension('.envied.g.part'), + ), + contains("static const String apiUrl = 'https://package.example';"), + ); + }); + + test('loads inherited env files from the input package', () async { + final AssetId input = AssetId('envied_generator', 'lib/inherited_env.dart'); + final AssetId defaults = AssetId('envied_generator', 'config/defaults.env'); + final AssetId env = AssetId('envied_generator', 'config/local.env'); + + final TestBuilderResult result = await _build(input, { + '$input': ''' +import 'package:envied/envied.dart'; + +@Envied( + path: 'config/local.env', + inheritFrom: ['config/defaults.env'], + requireEnvFile: true, +) +abstract class InheritedEnv { + @EnviedField() + static const String? apiUrl = null; + + @EnviedField() + static const String? feature = null; +} +''', + '$defaults': ''' +apiUrl=https://default.example +feature=off +''', + '$env': 'apiUrl=https://local.example', + }); + + expect(result.succeeded, isTrue); + expect(result.readerWriter.testing.inputsTracked, contains(defaults)); + expect(result.readerWriter.testing.inputsTracked, contains(env)); + expect( + result.readerWriter.testing.readString( + input.changeExtension('.envied.g.part'), + ), + allOf( + contains("static const String apiUrl = 'https://local.example';"), + contains("static const String feature = 'off';"), + ), + ); + }); +} + +Future _build( + AssetId input, + Map sourceAssets, +) async { + final TestReaderWriter readerWriter = TestReaderWriter( + rootPackage: input.package, + ); + await readerWriter.testing.loadIsolateSources(); + + return testBuilder( + enviedBuilder(BuilderOptions.empty), + sourceAssets, + rootPackage: input.package, + readerWriter: readerWriter, + flattenOutput: true, + ); +}