Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
128 changes: 125 additions & 3 deletions lib/src/commands/build_command.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import 'dart:io';
import 'package:args/command_runner.dart';
import 'package:path/path.dart' as p;

import 'build_yaml_guard.dart';

class BuildCommand extends Command {
@override
Expand Down Expand Up @@ -40,6 +43,10 @@ class BuildCommand extends Command {
await _cleanBuildCache();
}

// Self-healing pre-flight (zuraffa#276): ensure build.yaml registers the
// zorphy builder, otherwise build_runner exits 0 having written 0 outputs
// and we'd silently report success. Run BEFORE announcing the build so a
// misconfigured project fails fast without invoking build_runner.
if (dryRun) {
print('🔍 Dry-run mode: previewing changes...');
print(' Entities: $entityCount, Dart files: $dartFileCount');
Expand All @@ -50,20 +57,31 @@ class BuildCommand extends Command {
' 🧠 Smart merge: will preserve user code and @preserve blocks',
);
}
_reportBuildYamlDryRun();
print('');
} else {
print('🔨 Running build_runner build...');
print(' Entities: $entityCount, Dart files: $dartFileCount');
if (force) {
print(' ⚠️ Force mode: regenerating from scratch');
}
final guardResult = await _ensureBuildYaml();
if (!guardResult) {
// _ensureBuildYaml already printed an actionable error.
exit(1);
}
print('🔨 Running build_runner build...');
}

final exitCode = await _runBuild();

if (exitCode == 0) {
if (!dryRun) print('');
print(dryRun ? '✅ Dry-run completed' : '✅ Build completed successfully');
// Safety net: if build_runner wrote 0 outputs while @Zorphy sources
// exist, warn loudly so silent regressions don't slip through.
if (!dryRun) {
_warnIfNoOutputsGenerated();
}
} else if (!clean) {
print(
'\n⚠️ Build failed (exit $exitCode). Retrying with clean cache...',
Expand All @@ -72,6 +90,7 @@ class BuildCommand extends Command {
final retryCode = await _runBuild();
if (retryCode == 0) {
print('\n✅ Build completed successfully after cache clean');
_warnIfNoOutputsGenerated();
} else {
print('\n❌ Build failed with exit code $retryCode');
}
Expand All @@ -80,12 +99,115 @@ class BuildCommand extends Command {
}
}

/// Ensures `build.yaml` is in a state that lets build_runner produce zorphy
/// outputs. Returns `true` when the build may proceed; `false` when the
/// project has a `build.yaml` that omits the zorphy builder (the caller
/// should abort — the user must fix their config).
Future<bool> _ensureBuildYaml() async {
final status = BuildYamlGuard.check();
switch (status) {
case BuildYamlStatus.ok:
return true;
case BuildYamlStatus.missing:
print(
'🛠 No build.yaml found — scaffolding one that registers the zorphy builder.',
);
await BuildYamlGuard.scaffold();
print(' Created: build.yaml');
return true;
case BuildYamlStatus.missingZorphyBuilder:
print(BuildYamlGuard.missingZorphyBuilderMessage);
return false;
}
}

/// Dry-run counterpart of [_ensureBuildYaml]: reports what would happen
/// without writing.
void _reportBuildYamlDryRun() {
final status = BuildYamlGuard.check();
switch (status) {
case BuildYamlStatus.ok:
break;
case BuildYamlStatus.missing:
print(' Would scaffold: build.yaml (registers zorphy builder)');
break;
case BuildYamlStatus.missingZorphyBuilder:
print(
' ⚠️ build.yaml exists but omits the zorphy builder — build would write 0 outputs.',
);
print(' Add `zorphy:zorphy` under targets.\$default.builders.');
break;
}
}

/// Warns (non-fatally) when build_runner exited 0 but produced no `.zorphy`
/// / `.g.dart` outputs despite `@Zorphy`-annotated sources being present.
/// Catches misconfigurations the pre-flight check can't detect statically.
void _warnIfNoOutputsGenerated() {
try {
final hasZorphySources = _hasZorphyAnnotatedSources();
final hasOutputs = _hasGeneratedOutputs();
if (hasZorphySources && !hasOutputs) {
print(
'\n⚠️ build_runner wrote 0 outputs although @Zorphy sources exist.\n'
' Check build.yaml registers `zorphy:zorphy` for lib/src/** and\n'
' that the annotated files are under the configured generate_for\n'
' glob. Run `zfa setup` to regenerate a known-good build.yaml.',
);
}
} catch (_) {
// Best-effort safety net — never fail the build from this path.
}
}

bool _hasGeneratedOutputs() {
final libDir = Directory('lib');
if (!libDir.existsSync()) return false;
bool found = false;
for (final entity in libDir.listSync(recursive: true)) {
if (entity is File) {
final name = p.basename(entity.path);
if (name.endsWith('.zorphy.dart') || name.endsWith('.g.dart')) {
found = true;
break;
}
}
}
return found;
Comment on lines +143 to +176

Copy link
Copy Markdown
Contributor

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 @Zorphy sources, and how does it interact with source_gen combining_builder outputs?

💡 Result:

The Zorphy builder generates code for classes annotated with @Zorphy by producing files with the.zorphy.dart extension [1][2]. Additionally, classes annotated with @Zorphy2 generate 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:

#!/bin/bash
set -euo pipefail

echo "== locate files =="
fd -a 'build_command\.dart|build\.yaml|pubspec\.yaml' . | sed 's#^\./##'

echo "== build_command outline =="
ast-grep outline lib/src/commands/build_command.dart --view expanded || true

echo "== relevant build_command lines =="
sed -n '1,240p' lib/src/commands/build_command.dart | cat -n

echo "== search zorphy annotations/build usage =="
rg -n "`@Zorphy`|zorphy|\.zorphy|zorphy_annotation|zorphy_builder|build_runner|generate_for|build.yaml" -S .

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'; @Zorphy abstract 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 build

Citations:


🌐 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 @Zorphy2 annotation, which is designed to build before the primary Zorphy generator [3][4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== build.yaml =="
cat -n build.yaml

echo "== build_command_test relevant section =="
fd 'build_command_test\.dart' test -a | while read -r f; do
  echo "--- $f"
  sed -n '1,260p' "$f" | cat -n
done

echo "== deterministic path probe from lib/src/commands/build_command.dart text =="
python3 - <<'PY'
from pathlib import Path
import re

src = Path('lib/src/commands/build_command.dart').read_text()
print("has _hasGeneratedOutputs:", '_hasGeneratedOutputs()' in src)
print("generator accepts .zorphy.dart:", ".zorphy.dart" in src)
print("generator accepts .g.dart:", ".g.dart" in src)
print("skips annotated files:", ".zorphy.dart' || name.endsWith('.g.dart')" in src)
print("checks annotated `@Zorphy` or `@ZorphyMixin`:", '`@Zorphy`" || src.contains("`@ZorphyMixin`")' in src)
print("uses hasZorphySources and !hasOutputs:", 'hasZorphySources && !hasOutputs' in src)

m = re.search(r"bool _hasGeneratedOutputs\(\).*?\n  \}", src, re.S)
if not m:
    raise SystemExit("_hasGeneratedOutputs not found")
print("implementation:")
print(m.group(0), end="")

m = re.search(r"bool _hasZorphyAnnotatedSources\(\).*?\n  \}", src, re.S)
if not m:
    raise SystemExit("_hasZorphyAnnotatedSources not found")
print("implementation:")
print(m.group(0), end="")

print("\n== possible config that keeps unrelated output while missing zorphy output ==")
print("build.yaml contains zorphy:zorphy:", ":zorphy:" in Path('build.yaml').read_text())
print("build.yaml generate_for entries:")
for line in Path('build.yaml').read_text().splitlines():
    if 'generate_for' in line or 'builder' in line or re.search(r'^\s+src:', line):
        print(line)
PY

echo "== existing tests mentioning generated output warning =="
rg -n "wrote 0 outputs|`@Zorphy` sources|generated output|hasGeneratedOutputs|_warnIfNoOutputsGenerated" test lib -S

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_gen framework to generate code [1][2][3]. According to the project's documentation, Zorphy generates files with the extension .zorphy.dart (and .zorphy2.dart for specific annotations) rather than the standard .g.dart extension typically used by SharedPartBuilder [1][2]. Regarding @ZorphyMixin, while the @Zorphy annotation is the primary mechanism for generating immutable data classes, Zorphy's build system is integrated into build_runner and configured in build.yaml [1][2][3]. The generator produces code that includes standard Dart features like copyWith, JSON serialization, equality, and toString [1][4]. If you are encountering issues related to .g.dart output extensions, it is important to note that source_gen strongly recommends that only SharedPartBuilder uses the .g.dart extension 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.dart output process [1][2]. Ensure your build.yaml correctly targets the zorphy builder, 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 the part directive for the Zorphy-generated file [1][4].

Citations:


Correlate outputs with the annotated source set.

_hasGeneratedOutputs() treats any .zorphy.dart or .g.dart file under lib as a Zorphy-generated output. If another builder generates a file while a @Zorphy / @ZorphyMixin source lacks its part '...zorphy.dart' or was filtered by build.yaml, the warning is suppressed. Check outputs per annotated source set and add a test with an unrelated existing generated file, unless .g.dart is already covered by another generator in every supported project scenario.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/commands/build_command.dart` around lines 143 - 176, Update
_hasGeneratedOutputs and _warnIfNoOutputsGenerated to correlate generated
outputs with each `@Zorphy/`@ZorphyMixin-annotated source, rather than treating
any lib .zorphy.dart or .g.dart file as sufficient. Ensure sources missing their
expected part output or excluded by build.yaml still trigger the warning, while
preserving the best-effort non-fatal behavior; add coverage for an unrelated
existing generated file unless .g.dart is guaranteed to be exclusively produced
by Zorphy.

}

bool _hasZorphyAnnotatedSources() {
final libDir = Directory('lib');
if (!libDir.existsSync()) return false;
bool found = false;
for (final entity in libDir.listSync(recursive: true)) {
if (entity is File && entity.path.endsWith('.dart')) {
// Skip generated files themselves.
final name = p.basename(entity.path);
if (name.endsWith('.zorphy.dart') || name.endsWith('.g.dart')) continue;
try {
final src = entity.readAsStringSync();
if (src.contains('@Zorphy') || src.contains('@ZorphyMixin')) {
found = true;
break;
}
} catch (_) {
// Ignore unreadable files.
}
}
}
return found;
}

Future<int> _runBuild() async {
final args = [
// `--delete-conflicting-outputs` was removed in build_runner 2.16.0 and
// emits a "These options have been removed" warning on every invocation.
// build_runner now resolves conflicting outputs via the build cache, so
// the flag is no longer needed.
final args = <String>[
'run',
'build_runner',
'build',
'--delete-conflicting-outputs',
];

final process = await Process.start(
Expand Down
93 changes: 93 additions & 0 deletions lib/src/commands/build_yaml_guard.dart
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to the official Dart documentation, how can File creation be made exclusive so a write does not overwrite a file created concurrently?

💡 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 exclusive parameter of the File.create method [1]. When you call File.create with exclusive: true, the operation will fail if the file already exists [1]. In this case, the returned Future will complete with a PathExistsException [1]. If exclusive is false (the default), the file will be created if it does not exist, but existing files will be left untouched [1]. Example usage: dart import 'dart:io'; Future<void> createExclusive(String path) async { final file = File(path); try { await file.create(exclusive: true); print('File created successfully.'); } on PathExistsException { print('File already exists; skipping creation to avoid overwrite.'); } catch (e) { print('An error occurred: $e'); } } Note that Dart's file locking mechanisms (via RandomAccessFile.lock) are advisory and do not prevent other processes or parts of the system from opening, truncating, or deleting a file if they do not also check for those locks [2][3]. Therefore, for preventing concurrent overwrites during initial creation, using the atomic exclusive option in File.create is the intended mechanism [1].

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 || true

Repository: 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"
fi

Repository: arrrrny/zuraffa

Length of output: 12810


Make build.yaml scaffolding create the file exclusively.

BuildCommand._ensureBuildYaml() checks build.yaml, then BuildYamlGuard.scaffold() calls writeAsString at a later async point. Dart File.writeAsString() overwrites an existing file. Use File.create(exclusive: true) before writing, re-run the guard on PathExistsException, and only write after confirming the file was missed in the original check.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/commands/build_yaml_guard.dart` around lines 54 - 57, Update
BuildYamlGuard.scaffold and BuildCommand._ensureBuildYaml to create build.yaml
exclusively: after the initial missing-file check, have scaffold call
File.create(exclusive: true) before writing content, so existing files are never
overwritten. Catch PathExistsException in _ensureBuildYaml, rerun the
guard/check, and only write when the file was confirmed absent during the
original check.

}

/// 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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

For build_runner build.yaml configuration, how is a builder enabled or disabled under targets.$default.builders, and does a zorphy:zorphy key outside that map activate the builder?

💡 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'
fi

Repository: arrrrny/zuraffa

Length of output: 12286


Validate the active Zorphy builder configuration.

BuildYamlGuard._registersZorphyBuilder() returns BuildYamlStatus.ok for disabled or root-level zorphy:zorphy keys, while build_runner only enables builders under targets.$default.builders. Reject those cases by requiring targets.$default.builders.zorphy:zorphy with enabled: true, or use generate_for as a fallback. Add tests for disabled target registration and unrelated root zorphy:zorphy keys.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/src/commands/build_yaml_guard.dart` around lines 66 - 71, Update
BuildYamlGuard._registersZorphyBuilder to inspect only targets.$default.builders
and require zorphy:zorphy to be enabled, accepting generate_for as the fallback
configuration. Reject disabled registrations and unrelated root-level
zorphy:zorphy keys, and add tests covering both cases.

}

/// 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.
''';
}
Loading
Loading