-
Notifications
You must be signed in to change notification settings - Fork 2
fix(zfa build): scaffold build.yaml when missing, fail loudly when zorphy builder unregistered (#276) #282
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import 'dart:io'; | ||
|
|
||
| import 'package:path/path.dart' as p; | ||
|
|
||
| import '../core/dependencies/dependency_wirer.dart'; | ||
|
|
||
| /// Outcome of inspecting a project's `build.yaml` from the build command. | ||
| enum BuildYamlStatus { | ||
| /// No `build.yaml` exists at the project root. [BuildYamlGuard.scaffold] | ||
| /// can create one that registers the zorphy builder. | ||
| missing, | ||
|
|
||
| /// A `build.yaml` exists but it does not register the `zorphy:zorphy` | ||
| /// builder. build_runner will exit 0 having written 0 outputs, silently. | ||
| /// The user must be told to add the zorphy builder registration. | ||
| missingZorphyBuilder, | ||
|
|
||
| /// A `build.yaml` exists and registers the `zorphy:zorphy` builder. | ||
| ok, | ||
| } | ||
|
|
||
| /// Self-healing guard for `zfa build`. | ||
| /// | ||
| /// Before delegating to `build_runner`, [BuildCommand] asks this guard whether | ||
| /// the project's `build.yaml` is in a state that will actually produce | ||
| /// `.zorphy.dart` / `.g.dart` outputs. When the file is missing, the build | ||
| /// command scaffolds it (reusing [DependencyWirer.buildYamlContent]); when the | ||
| /// file exists but omits the zorphy builder, the build command fails loudly | ||
| /// with an actionable error instead of silently reporting success with 0 | ||
| /// outputs (zuraffa#276). | ||
| class BuildYamlGuard { | ||
| /// The builder key that must appear in `build.yaml` for zorphy codegen to | ||
| /// run. Matches `DependencyWirer.buildYamlContent`. | ||
| static const String zorphyBuilderKey = 'zorphy:zorphy'; | ||
|
|
||
| /// Returns the current [BuildYamlStatus] for [projectRoot] | ||
| /// (defaults to [Directory.current]). | ||
| static BuildYamlStatus check({String? projectRoot}) { | ||
| final root = projectRoot ?? Directory.current.path; | ||
| final file = File(p.join(root, 'build.yaml')); | ||
| if (!file.existsSync()) { | ||
| return BuildYamlStatus.missing; | ||
| } | ||
| final contents = file.readAsStringSync(); | ||
| if (!_registersZorphyBuilder(contents)) { | ||
| return BuildYamlStatus.missingZorphyBuilder; | ||
| } | ||
| return BuildYamlStatus.ok; | ||
| } | ||
|
|
||
| /// Scaffolds a `build.yaml` that registers the zorphy + json_serializable | ||
| /// builders. Only call when [check] returned [BuildYamlStatus.missing] — | ||
| /// never overwrite an existing `build.yaml`. | ||
| static Future<void> scaffold({String? projectRoot}) async { | ||
| final root = projectRoot ?? Directory.current.path; | ||
| final file = File(p.join(root, 'build.yaml')); | ||
| await file.writeAsString(DependencyWirer.buildYamlContent); | ||
|
Comment on lines
+54
to
+57
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: According to the official Dart documentation, the recommended way to create a file exclusively—ensuring a write does not overwrite a file if it has been created concurrently—is to use the Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== files =="
git ls-files | rg '(^|/)build_yaml_guard\.dart$|build|yaml' || true
echo "== outline =="
ast-grep outline lib/src/commands/build_yaml_guard.dart --view expanded || true
echo "== relevant file =="
cat -n lib/src/commands/build_yaml_guard.dart
echo "== callers / usage =="
rg -n "BuildYamlGuard|_ensureBuildYaml|scaffold\\(|ensureBuildYaml" lib test 2>/dev/null || trueRepository: arrrrny/zuraffa Length of output: 13091 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== build_command relevant sections =="
cat -n lib/src/commands/build_command.dart | sed -n '1,180p'
echo "== tests outline/head =="
cat -n test/commands/build_yaml_guard_test.dart | sed -n '1,190p'
echo "== behavioral probe: Dart File.create write/overwrite semantics =="
tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
cat > "$tmpdir/semantics_probe.dart" <<'DART'
import 'dart:io';
Future<void> main() async {
final file = File('$tmpdir/build.yaml');
await file.writeAsString('initial');
print('initial=${file.readAsStringSync()}');
await file.writeAsString('overwritten');
print('after_writeAsString=${file.readAsStringSync()}');
try {
await file.create(exclusive: true);
print('exclusive_create=no exception');
} on PathExistsException catch (e) {
print('exclusive_create=PathExistsException: ${e.message}');
} catch (e) {
print('exclusive_create=other ${e.runtimeType}: $e');
}
}
DART
if command -v dart >/dev/null 2>&1; then
dart "$tmpdir/semantics_probe.dart"
else
echo "dart not available"
fiRepository: arrrrny/zuraffa Length of output: 12810 Make
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /// True when [contents] enables the `zorphy:zorphy` builder. | ||
| /// | ||
| /// Accepts either the `builders:` mapping form or a bare `zorphy:zorphy` | ||
| /// reference, and tolerates leading whitespace. This is intentionally a | ||
| /// substring/regex check rather than a full YAML parse so the guard stays | ||
| /// dependency-free and robust to hand-edited formatting. | ||
| static bool _registersZorphyBuilder(String contents) { | ||
| if (contents.isEmpty) return false; | ||
| // Match `zorphy:zorphy` possibly followed by `:` (mapping) or whitespace, | ||
| // at the start of a line (builder keys are map keys under `builders:`). | ||
| final re = RegExp(r'^\s*zorphy:zorphy\s*:?', multiLine: true); | ||
| return re.hasMatch(contents); | ||
|
Comment on lines
+66
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🌐 Web query:
💡 Result: In a build_runner build.yaml configuration, builders are enabled or disabled within the targets.$default.builders map by setting the enabled property [1][2][3]. Setting enabled: false explicitly disables the builder for that specific target [4][2]. Conversely, setting enabled: true ensures the builder is active [2][3]. If this key is omitted, the builder's state is determined by its default auto_apply configuration [2][3]. Regarding the zorphy:zorphy key, placing it outside the targets section (e.g., at the root of the build.yaml) does not activate or configure the builder. The build_runner configuration strictly expects builder-specific settings to be defined within the targets section [1][2]. In the case of the zorphy package, the common usage shown in documentation (e.g., targets: $default: builders: zorphy:zorphy: { enabled: true }) is the correct way to register and enable that specific builder [5][6][7][8]. Any configuration key placed outside the structure defined by the build_config specification is ignored by the build_runner process [1][2]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Files matching build_yaml_guard:\n'
fd -a 'build_yaml_guard\.dart$|zorphy|build\.yaml' . | sed 's#^\./##' | head -100
printf '\nbuild_yaml_guard outline:\n'
ast-grep outline lib/src/commands/build_yaml_guard.dart --view expanded || true
printf '\nRelevant build_yaml_guard contents:\n'
cat -n lib/src/commands/build_yaml_guard.dart
printf '\nSearch for BuildYamlGuard usages and tests:\n'
rg -n "BuildYamlGuard|build.yaml|zorphy:zorphy|_registersZorphyBuilder|BuildYamlStatus" .Repository: arrrrny/zuraffa Length of output: 21524 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Current build.yaml:\n'
cat -n build.yaml
printf '\nbuild_command relevant contents:\n'
sed -n '40,165p' lib/src/commands/build_command.dart | cat -n
printf '\nExisting build_yaml_guard tests:\n'
cat -n test/commands/build_yaml_guard_test.dart
printf '\nBehavior simulation:_registersZorphyBuilder matches these configurations?\n'
python3 - <<'PY'
import re
import tempfile
os, makedirs, chdir = __import__('os'), __import__('os').makedirs, __import__('os').chdir
re = __import__('re')
rx = re.compile(r'^\s*zorphy:zorphy\s*:?', re.MULTILINE)
configs = {
'root zorphy key': 'zorphy:zorphy:\n enabled: true\n',
'disabled under default target builder': '''targets:
$default:
builders:
zorphy:zorphy:
enabled: false
generate_for:
- lib/src/**
''',
'enabled under default target builder': '''targets:
$default:
builders:
zorphy:zorphy:
enabled: true
generate_for:
- lib/src/**
- test/**
''',
'root builder key with disabled mapping inside': '''zorphy:zorphy:
enabled: false
generate_for:
- lib/src/**
''',
}
for name, s in configs.items():
print(f'{name}: {bool(rx.search(s))}')
PY
printf '\nFlutter/Dart package analyzer availability:\n'
if command -v flutter >/dev/null 2>&1; then
flutter --version 2>&1 | sed -n '1,5p'
fi
if command -v dart >/dev/null 2>&1; then
dart --version 2>&1 | sed -n '1,5p'
fiRepository: arrrrny/zuraffa Length of output: 12286 Validate the active Zorphy builder configuration.
🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| /// The actionable error message shown when `build.yaml` exists but does not | ||
| /// register the zorphy builder. Kept here so tests can assert against it. | ||
| static const String missingZorphyBuilderMessage = ''' | ||
| ❌ build.yaml exists but does not register the zorphy builder. | ||
|
|
||
| build_runner ran successfully but wrote 0 outputs because no builder is | ||
| configured to process @Zorphy annotations. Add the following under | ||
| `targets:\$default.builders:` in build.yaml: | ||
|
|
||
| builders: | ||
| zorphy:zorphy: | ||
| enabled: true | ||
| generate_for: | ||
| - lib/src/** | ||
| - test/** | ||
|
|
||
| Alternatively, run `zfa setup` to regenerate a correct build.yaml, or | ||
| delete build.yaml and re-run `zfa build` to have it scaffolded for you. | ||
| '''; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🌐 Web query:
What output file names does the zorphy builder generate for@Zorphysources, and how does it interact with source_gen combining_builder outputs?💡 Result:
The Zorphy builder generates code for classes annotated with
@Zorphyby producing files with the.zorphy.dart extension [1][2]. Additionally, classes annotated with@Zorphy2generate files with the.zorphy2.dart extension [1][3]. Regarding interaction with source_gen's combining_builder: The Zorphy builder functions as part of the standard Dart build_runner process [1][4]. While Zorphy utilizes source_gen in its implementation, it operates by generating specific part files (.zorphy.dart) associated with the source code [1][2]. The.zorphy2.dart files are designed to build prior to the main Zorphy generation [1][3]. In the context of build_runner, these generated part files are typically handled by the combining_builder (or similar mechanisms in the build system) to merge the generated code into the final project structure [5].Citations:
🏁 Script executed:
Repository: arrrrny/zuraffa
Length of output: 50371
🌐 Web query:
zorphy pub.dev source_gen builder .zorphy.dart annotation part files💡 Result:
Zorphy is a code generation package for Dart and Flutter that automates the creation of boilerplate code such as copyWith methods, JSON serialization, equality operators, toString methods, and patch mechanisms [1][2][3]. To use Zorphy with your Dart/Flutter projects, you must include the required dependencies in your pubspec.yaml file [1][2]: dependencies: zorphy_annotation: ^1.7.0 dev_dependencies: zorphy: ^1.7.0 build_runner: ^2.4.0 As Zorphy utilizes the Dart source_gen and build_runner infrastructure, it requires you to define a part file in your source code [3][4]. By convention, Zorphy uses the.zorphy.dart file extension for these generated parts [3]. To implement this in a file (e.g., user.dart), you must: 1. Import the zorphy_annotation package [3]. 2. Declare the part directive pointing to the.zorphy.dart file [3]. 3. Annotate your classes with the appropriate Zorphy annotations (e.g.,
@Zorphy) to trigger the code generation [5][3]. Example structure: import 'package:zorphy_annotation/zorphy_annotation.dart'; part 'user.zorphy.dart';@Zorphyabstract class User { String get name; int get age; } After setting up your files, run the build_runner command in your terminal to generate the necessary code [1][3]: dart run build_runner buildCitations:
🌐 Web query:
zorphy_annotation AnnotationSourceGenerator .zorphy.dart generated file extension💡 Result:
In the Zorphy code generation package for Dart and Flutter, the.zorphy.dart file extension is the standard naming convention for generated part files [1][2][3]. When you annotate an abstract class with
@Zorphy, you must include a part directive in your source file that matches the filename with a.zorphy.dart extension (e.g., if your file is named user.dart, you should add part 'user.zorphy.dart';) [1][3][4]. The Zorphy generator then creates the corresponding code—such as constructors, copyWith methods, JSON serialization, and equality operators—in that generated file [1][5]. Additionally, the package uses the.zorphy2.dart extension for code generated from the@Zorphy2annotation, which is designed to build before the primary Zorphy generator [3][4].Citations:
🏁 Script executed:
Repository: arrrrny/zuraffa
Length of output: 13655
🌐 Web query:
zorphy source_gen builder .g.dart output extension@ZorphyMixin``💡 Result:
The Zorphy package uses the
source_genframework to generate code [1][2][3]. According to the project's documentation, Zorphy generates files with the extension.zorphy.dart(and.zorphy2.dartfor specific annotations) rather than the standard.g.dartextension typically used bySharedPartBuilder[1][2]. Regarding@ZorphyMixin, while the@Zorphyannotation is the primary mechanism for generating immutable data classes, Zorphy's build system is integrated intobuild_runnerand configured inbuild.yaml[1][2][3]. The generator produces code that includes standard Dart features likecopyWith, JSON serialization, equality, andtoString[1][4]. If you are encountering issues related to.g.dartoutput extensions, it is important to note thatsource_genstrongly recommends that onlySharedPartBuilderuses the.g.dartextension to avoid conflicts [5][6][7]. Because Zorphy utilizes its own dedicated builder and file extensions (.zorphy.dart), it operates independently of the standard.g.dartoutput process [1][2]. Ensure yourbuild.yamlcorrectly targets thezorphybuilder, which is typically configured as follows: targets: $default: builders: zorphy|zorphy: enabled: true If you are manually implementing a mixin to work with Zorphy-generated classes, you should ensure that your mixin is applied to the class before the code generation runs, or that it is properly imported in the file that includes thepartdirective for the Zorphy-generated file [1][4].Citations:
Correlate outputs with the annotated source set.
_hasGeneratedOutputs()treats any.zorphy.dartor.g.dartfile underlibas a Zorphy-generated output. If another builder generates a file while a@Zorphy/@ZorphyMixinsource lacks itspart '...zorphy.dart'or was filtered bybuild.yaml, the warning is suppressed. Check outputs per annotated source set and add a test with an unrelated existing generated file, unless.g.dartis already covered by another generator in every supported project scenario.🤖 Prompt for AI Agents