Skip to content

fix(zfa build): scaffold build.yaml when missing, fail loudly when zorphy builder unregistered (#276) - #282

Merged
arrrrny merged 1 commit into
developmentfrom
fix/zfa-build-scaffold-build-yaml-276
Aug 10, 2026
Merged

fix(zfa build): scaffold build.yaml when missing, fail loudly when zorphy builder unregistered (#276)#282
arrrrny merged 1 commit into
developmentfrom
fix/zfa-build-scaffold-build-yaml-276

Conversation

@arrrrny

@arrrrny arrrrny commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Closes #276

When build.yaml is absent, zfa build now scaffolds one registering the zorphy builder (self-healing). When build.yaml exists but omits zorphy:zorphy, zfa build fails loudly with an actionable error and exit 1 instead of silently reporting success with 0 outputs.

Root cause

BuildCommand ran dart run build_runner build and reported success whenever the process exited 0 — but with no build.yaml (or one that did not register zorphy:zorphy), build_runner exited 0 having written 0 outputs. No zfa command created build.yaml, so users who skipped zfa init/zfa setup were stuck.

Secondary: build_runner 2.16.0 removed --delete-conflicting-outputs, producing the W These options have been removed warning quoted in the issue.

Fix

New lib/src/commands/build_yaml_guard.dart exposes:

  • BuildYamlStatus check()missing | missingZorphyBuilder | ok
  • scaffold() → writes DependencyWirer.buildYamlContent
  • missingZorphyBuilderMessage → actionable error text

BuildCommand.run() now does a pre-flight before invoking build_runner:

  • missing → scaffold build.yaml, log, proceed (self-healing, option a)
  • missingZorphyBuilder → print actionable error, exit(1) without invoking build_runner (fail loudly, option b)
  • ok → proceed

Plus:

  • Post-flight safety net: after a 0-exit build, if @Zorphy sources exist but no .zorphy.dart/.g.dart were produced, print a loud warning.
  • Dropped --delete-conflicting-outputs from _runBuild() args (removed in build_runner 2.16.0).

Dry-run reports the pre-flight plan (Would scaffold… / omits the zorphy builder) without writing.

Tests

  • test/commands/build_yaml_guard_test.dart — 10 unit tests (missing/ok/missingZorphyBuilder/empty/indented/comment-false-positive/scaffold-roundtrip/scaffold-content/default-cwd/actionable-message)
  • test/commands/build_command_test.dart — 5 subprocess integration tests (--help, dry-run missing, dry-run misconfigured, build scaffolds-then-proceeds, build fails-loudly)

Verification

  • dart analyze on all 4 files: no issues
  • dart test full suite: 1162/1162 pass
  • Reference app (zuraffa repo, correct build.yaml): zfa build --dry-run → pre-flight ok, build_runner writes 28 outputs, no deprecated-flag warning
  • Issue repro (no build.yaml): zfa build scaffolds build.yaml, proceeds
  • Misconfigured build.yaml: zfa build fails loudly, exit 1, build_runner not invoked, user file untouched

Summary by CodeRabbit

  • New Features
    • Automatically creates a missing build.yaml before building.
    • Validates builder configuration and provides actionable guidance when setup is incomplete.
    • Dry-run mode now reports configuration scaffolding actions.
  • Bug Fixes
    • Prevents builds from proceeding when the required builder is not configured.
    • Warns when annotated source files do not produce generated outputs, including after cache-clean retries.
    • Removed the obsolete conflicting-output option from build execution.

@arrrrny

arrrrny commented Aug 10, 2026

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 434f96a9-ee39-4cd6-a0ab-7a460d37a8be

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

zfa build now validates and scaffolds build.yaml before invoking build_runner. It fails on missing zorphy builder registration, reports dry-run actions, warns when annotated sources produce no generated files, and removes the obsolete conflict-output argument.

Changes

Build preflight and output validation

Layer / File(s) Summary
Build.yaml guard and scaffolding
lib/src/commands/build_yaml_guard.dart, test/commands/build_yaml_guard_test.dart
BuildYamlGuard detects missing, incomplete, and valid configurations. It scaffolds missing files and has coverage for detection, parsing, defaults, and messages.
Build preflight integration
lib/src/commands/build_command.dart, test/commands/build_command_test.dart
zfa build validates or creates build.yaml before build_runner. Dry-run output reports the action, and invalid configuration stops the build.
Generated-output validation and build invocation
lib/src/commands/build_command.dart
The command scans annotated Dart sources and generated outputs. It warns when no outputs are produced and removes --delete-conflicting-outputs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Developer
  participant ZfaBuild
  participant BuildYamlGuard
  participant build_runner
  participant ProjectFiles
  Developer->>ZfaBuild: run zfa build
  ZfaBuild->>BuildYamlGuard: check or scaffold build.yaml
  BuildYamlGuard->>ProjectFiles: read or write build.yaml
  BuildYamlGuard-->>ZfaBuild: return configuration status
  ZfaBuild->>build_runner: run configured build
  build_runner->>ProjectFiles: write generated outputs
  ZfaBuild->>ProjectFiles: scan annotated sources and outputs
  ZfaBuild-->>Developer: report build result or warning
Loading

Possibly related PRs

  • arrrrny/zuraffa#191: Related to the DDA build pipeline and annotation-driven code generation.
  • arrrrny/zuraffa#277: Introduced DependencyWirer.buildYamlContent, which this PR uses for scaffolding.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The changes satisfy issue #276 by scaffolding build.yaml, detecting missing zorphy registration, failing clearly, and testing these workflows.
Out of Scope Changes check ✅ Passed The build guard, preflight checks, post-build warning, flag removal, and tests directly support the linked issue and PR objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: scaffolding missing build.yaml and failing when the zorphy builder is not registered.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor
⚠️ Action not completed

Already reviewed.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/src/commands/build_command.dart (1)

42-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Prevent side effects in dry-run mode.

dryRun changes output text but does not stop normal build execution.

  • lib/src/commands/build_command.dart#L42-L44: do not clean the build cache when dryRun is true.
  • lib/src/commands/build_command.dart#L75-L75: return after dry-run reporting instead of invoking build_runner.
🤖 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 42 - 44, Update
lib/src/commands/build_command.dart lines 42-44 so _cleanBuildCache() runs only
when clean is enabled and dryRun is false; update line 75 so the dry-run
reporting path returns before invoking build_runner. Use the existing build
command flow and dryRun flag, preserving normal execution when dryRun is false.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@lib/src/commands/build_command.dart`:
- Around line 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.

In `@lib/src/commands/build_yaml_guard.dart`:
- Around line 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.
- Around line 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.

In `@test/commands/build_command_test.dart`:
- Around line 35-46: Update the setUpAll setup around zfaBin and
useCompiledBinary to always invoke the checkout entrypoint at bin/zfa.dart,
removing the preference for $HOME/.local/bin/zfa. If startup performance
requires a compiled binary, build that binary from the current checkout before
assigning it to zfaBin.
- Around line 80-82: Update all five subprocess-output sites in
test/commands/build_command_test.dart (anchor lines 80-82 and sibling lines
99-101, 133-135, 162-164, and 200-201): start stdout and stderr stream
collectors before awaiting either result, then await both collectors
concurrently; in the malformed-build test, collect stderr before awaiting
exitCode.

---

Outside diff comments:
In `@lib/src/commands/build_command.dart`:
- Around line 42-44: Update lib/src/commands/build_command.dart lines 42-44 so
_cleanBuildCache() runs only when clean is enabled and dryRun is false; update
line 75 so the dry-run reporting path returns before invoking build_runner. Use
the existing build command flow and dryRun flag, preserving normal execution
when dryRun is false.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a2d0da6f-d24a-4d87-8e90-5e913add47a5

📥 Commits

Reviewing files that changed from the base of the PR and between 5a460c8 and 0ed7a4c.

📒 Files selected for processing (4)
  • lib/src/commands/build_command.dart
  • lib/src/commands/build_yaml_guard.dart
  • test/commands/build_command_test.dart
  • test/commands/build_yaml_guard_test.dart

Comment on lines +143 to +176
/// 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;

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.

Comment on lines +54 to +57
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);

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.

Comment on lines +66 to +71
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);

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.

Comment on lines +35 to +46
setUpAll(() async {
final homeDir = Platform.environment['HOME'] ?? '';
final compiledBin = path.join(homeDir, '.local', 'bin', 'zfa');
final compiledExists = File(compiledBin).existsSync();
if (compiledExists) {
zfaBin = compiledBin;
useCompiledBinary = true;
} else {
final projectRoot = await findProjectRoot();
zfaBin = path.join(projectRoot, 'bin', 'zfa.dart');
useCompiledBinary = false;
}

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

Run the command from the checkout.

This test uses $HOME/.local/bin/zfa when it exists. That binary can belong to another revision. The test can then pass or fail without testing this PR.

Always invoke the checkout entrypoint. Build a test binary from the checkout only if startup time requires it.

🤖 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 `@test/commands/build_command_test.dart` around lines 35 - 46, Update the
setUpAll setup around zfaBin and useCompiledBinary to always invoke the checkout
entrypoint at bin/zfa.dart, removing the preference for $HOME/.local/bin/zfa. If
startup performance requires a compiled binary, build that binary from the
current checkout before assigning it to zfaBin.

Comment on lines +80 to +82
final stdout = await proc.stdout.transform(systemEncoding.decoder).join();
final stderr = await proc.stderr.transform(systemEncoding.decoder).join();
final code = await proc.exitCode;

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

According to Dart Process documentation, should stdout and stderr be consumed concurrently to prevent a child process from blocking on full output pipes?

💡 Result:

Yes, according to Dart documentation and recommended best practices, stdout and stderr must be consumed concurrently to prevent a child process from blocking [1][2][3][4]. The underlying communication for stdout and stderr uses pipes with limited capacity [1][2][3]. If a child process writes to these pipes faster than they are being read—or if one stream is left unread while the other is filled—the child process will block, waiting for the pipe buffer to have available space [1][2][3]. If this happens, the child process will never finish, and any code waiting for the process to exit will deadlock [1][2][4]. To avoid this, you should ensure that both streams are being actively consumed, such as by using listen or other asynchronous consumption methods, before or while waiting for the process to complete [5][4]. For example, you can register listeners for both streams or use methods that process both streams in parallel to ensure that neither pipe buffer becomes full and stalls the subprocess [4]. Simply reading one stream to completion before starting to read the other is a common cause of deadlocks [4].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file snippets =="
sed -n '65,105p;125,170p;190,210p' test/commands/build_command_test.dart

echo
echo "== process.stdout/stderr usages in target file =="
rg -n "proc\.stdout|proc\.stderr|exitCode|Process\.start" test/commands/build_command_test.dart

Repository: arrrrny/zuraffa

Length of output: 5477


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== analyzer availability and package config =="
dart --version 2>&1 || true
if [ -f pubspec.yaml ]; then
  sed -n '1,120p' pubspec.yaml
fi

echo
echo "== full target test function headers =="
argparse parse test/commands/build_command_test.dart || true
fd -a build_command_test.dart . | head -10

echo
echo "== stream transformation structure =="
python3 - <<'PY'
from pathlib import Path
p = Path('test/commands/build_command_test.dart')
s = p.read_text()
for i, line in enumerate(s.splitlines(), 1):
    if 'await proc.stdout.transform(systemEncoding.decoder).join()' in line:
        start=max(1,i-2); end=min(len(s.splitlines()), i+4)
        print(f"\n-- occurrences around line {i} --")
        for n in range(start,end+1):
            print(f"{n}: {s.splitlines()[n-1]}")
PY

Repository: arrrrny/zuraffa

Length of output: 3870


Drain stdout and stderr concurrently.

Dart subprocess pipes have limited buffers, so awaiting stdout to completion before consuming stderr can make the child process block. Start both stream collectors before awaiting either result in these tests, and collect stderr before awaiting exitCode in the malformed-build test.

📍 Affects 1 file
  • test/commands/build_command_test.dart#L80-L82 (this comment)
  • test/commands/build_command_test.dart#L99-L101
  • test/commands/build_command_test.dart#L133-L135
  • test/commands/build_command_test.dart#L162-L164
  • test/commands/build_command_test.dart#L200-L201
🤖 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 `@test/commands/build_command_test.dart` around lines 80 - 82, Update all five
subprocess-output sites in test/commands/build_command_test.dart (anchor lines
80-82 and sibling lines 99-101, 133-135, 162-164, and 200-201): start stdout and
stderr stream collectors before awaiting either result, then await both
collectors concurrently; in the malformed-build test, collect stderr before
awaiting exitCode.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant