Skip to content

feat(build): guided build lifecycle and anvil build - #3

Merged
davidcreated merged 2 commits into
developfrom
feat/build-lifecycle
Jul 23, 2026
Merged

davidcreated merged 2 commits into
developfrom
feat/build-lifecycle

Conversation

@davidcreated

@davidcreated davidcreated commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

What

Milestone 2: the guided build lifecycle and the anvil build command.

  • Driver contract (internal/driver) with drivers for Flutter, React Native, native Android, native iOS, and (pulled forward) Swift (SPM) and Kotlin/JVM.
  • --flavor threaded into build and test steps (Flutter --flavor, Android assemble<Flavor><Type>, iOS -scheme, RN both).
  • Runner (internal/pipeline): streams combined output, captures exit codes, classifies, collects artifacts, fail-fasts.
  • internal/tui: Bubble Tea live view plus a plain non-TTY renderer (selected on non-TTY or CI).
  • anvil build: --path, --target, --flavor, --release, --dry-run, --plain.
  • Detection refinement: Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks.

All lifecycle phases are signing-safe (deps, analyze, test, unsigned/debug/simulator build). Signing and upload are Milestones 3 and 4.

Verification

  • ./check green (gofmt, vet, go test -race); coverage: detect 89%, pipeline 87%, driver 60%.
  • Runner tested via a real subprocess (streaming, exit codes, fail-fast, skip).
  • Smoke: anvil build --dry-run on ca-mobile (Flutter), flavor and target wiring, and the no-project error path.

Known limitations

  • iOS scheme is derived by convention or --flavor; flavor/scheme auto-detection is a later step.
  • Error surfacing is a bounded output tail; structured diagnostics parsing is a later enhancement.

Summary by CodeRabbit

  • New Features

    • Added anvil build with dependency, analysis, testing, and build phases.
    • Added dry-run plans, release and flavor options, target selection, artifact reporting, and interrupt handling.
    • Added support for Flutter, React Native, Android, iOS, Kotlin/JVM, Kotlin Multiplatform, and Swift projects.
    • Added anvil detect with table and JSON output.
    • Added interactive progress display with plain-text output for CI and non-interactive terminals.
  • Documentation

    • Expanded release notes, roadmap details, and build lifecycle guidance.

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.
@coderabbitai

coderabbitai Bot commented Jul 23, 2026 •

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c8bab17a-6eb7-4a37-a064-8992d2dee24e

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

The PR adds a guided build lifecycle: expanded project detection, stack-specific drivers, phase-based pipeline execution, streamed terminal rendering, and the anvil build command with target, flavor, release, dry-run, and plain-output options.

Changes

Guided build lifecycle

Layer / File(s) Summary
Project detection
internal/detect/*
Android Gradle detection is refined, while Kotlin JVM/Multiplatform and Swift Package projects are added to stack detection.
Driver contracts and implementations
internal/driver/*
Phase/status contracts, driver registration, and Flutter, React Native, Android, iOS, Kotlin, and Swift command generation are added with driver tests.
Pipeline planning and execution
internal/pipeline/*
The pipeline plans phases, streams command output, classifies results, collects artifacts, skips unsupported phases, and fails fast after an unsuccessful step.
Build command and output rendering
cmd/build.go, internal/tui/*, go.mod
The Cobra build command connects detection, drivers, pipeline execution, and interactive or plain event rendering.
Release and milestone documentation
CHANGELOG.md, docs/ROADMAP.md, tasks/todo.md
Documentation records the new build lifecycle, supported stacks, command options, and milestone status.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

  • openforge-oss/anvil#2: Earlier detection engine and CLI work extended by the Kotlin and Swift detection changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 17.95% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: the guided build lifecycle and the new anvil build command.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/build-lifecycle

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

@davidcreated
davidcreated merged commit 9705452 into develop Jul 23, 2026
5 of 6 checks passed

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 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 `@CHANGELOG.md`:
- Around line 14-16: Update CHANGELOG.md lines 14-16, docs/ROADMAP.md lines
10-16, and tasks/todo.md lines 41-44 to limit --flavor claims to the stacks that
currently support it, unless internal/driver/swift.go and
internal/driver/kotlin.go are updated to propagate opts.Flavor through their
build and test steps. Keep the documentation consistent across all three files.

In `@docs/ROADMAP.md`:
- Around line 9-10: Synchronize the Detection/Milestone 1 status between
docs/ROADMAP.md lines 9-10 and tasks/todo.md lines 39-49 so both sources use the
same completed or in-progress state; update only the inconsistent status entry
and preserve the surrounding roadmap and progress-board content.

In `@internal/driver/android.go`:
- Around line 7-32: Update Android.Steps and buildTask so non-empty Target
values are validated against the supported Android targets before constructing a
Gradle task. Preserve assemble for an empty target and bundle for aab,
appbundle, or bundle; return nil, false from Android.Steps for any other target,
matching Flutter.Steps behavior.

In `@internal/driver/driver.go`:
- Around line 124-127: Remove the unused dirExists helper from the driver
package, or integrate it into an existing directory-checking flow if that
behavior is required. Ensure the final implementation has no unused symbol while
preserving current functionality.

In `@internal/driver/ios.go`:
- Around line 21-30: Update the Test case in the driver step construction to
avoid hardcoding the iPhone 15 simulator destination. Make the xcodebuild test
destination configurable through the existing options/configuration flow, or
resolve an installed simulator dynamically, while preserving the current scheme
and test arguments.

In `@internal/driver/reactnative.go`:
- Around line 32-36: Defer the ios/*.xcworkspace lookup in the relevant
Steps/pipeline planning flow until execution after dependency installation,
rather than returning inapplicable when firstGlob finds nothing during planning.
Ensure the requested iOS build is retained on a clean checkout and resolve the
workspace at build time, with coverage for this clean-checkout sequence.
🪄 Autofix (Beta)

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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 0903b9fb-bad5-4dac-99a9-fd7a30ab943b

📥 Commits

Reviewing files that changed from the base of the PR and between 14f78e6 and d0357b1.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (25)
  • CHANGELOG.md
  • cmd/build.go
  • docs/ROADMAP.md
  • go.mod
  • internal/detect/android.go
  • internal/detect/detect.go
  • internal/detect/detect_test.go
  • internal/detect/ios.go
  • internal/detect/kotlin.go
  • internal/detect/swift.go
  • internal/driver/android.go
  • internal/driver/driver.go
  • internal/driver/driver_test.go
  • internal/driver/flutter.go
  • internal/driver/ios.go
  • internal/driver/kotlin.go
  • internal/driver/reactnative.go
  • internal/driver/registry.go
  • internal/driver/swift.go
  • internal/pipeline/pipeline.go
  • internal/pipeline/pipeline_test.go
  • internal/tui/plain.go
  • internal/tui/plain_test.go
  • internal/tui/tui.go
  • tasks/todo.md
💤 Files with no reviewable changes (1)
  • internal/detect/ios.go

Comment thread CHANGELOG.md
Comment on lines +14 to +16
- Driver contract (`internal/driver`) and drivers for Flutter, React Native,
native Android, native iOS, Swift (SPM), and Kotlin/JVM, with `--flavor`
threaded into build and test steps.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== File list ==\n'
git ls-files CHANGELOG.md docs/ROADMAP.md tasks/todo.md internal/driver | sed 's#^`#-` #'

printf '\n== Swift driver outline ==\n'
ast-grep outline internal/driver/swift.go --view expanded || true

printf '\n== Swift driver contents (relevant slice) ==\n'
nl -ba internal/driver/swift.go | sed -n '1,220p'

printf '\n== Roadmap slice ==\n'
nl -ba docs/ROADMAP.md | sed -n '1,120p'

printf '\n== Todo slice ==\n'
nl -ba tasks/todo.md | sed -n '1,120p'

printf '\n== Changelog slice ==\n'
nl -ba CHANGELOG.md | sed -n '1,80p'

printf '\n== Search for Flavor usage in driver code ==\n'
rg -n "Flavor|flavor" internal/driver docs tasks CHANGELOG.md

Repository: openforge-oss/anvil

Length of output: 774


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Swift driver ==\n'
sed -n '1,220p' internal/driver/swift.go | cat -n

printf '\n== Roadmap ==\n'
sed -n '1,120p' docs/ROADMAP.md | cat -n

printf '\n== Todo ==\n'
sed -n '1,120p' tasks/todo.md | cat -n

printf '\n== Changelog ==\n'
sed -n '1,80p' CHANGELOG.md | cat -n

printf '\n== Flavor references in driver code ==\n'
rg -n "Flavor|flavor" internal/driver

Repository: openforge-oss/anvil

Length of output: 10782


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Kotlin driver ==\n'
sed -n '1,220p' internal/driver/kotlin.go | cat -n

printf '\n== Flutter driver ==\n'
sed -n '1,220p' internal/driver/flutter.go | cat -n

printf '\n== Android driver ==\n'
sed -n '1,220p' internal/driver/android.go | cat -n

printf '\n== iOS driver ==\n'
sed -n '1,220p' internal/driver/ios.go | cat -n

Repository: openforge-oss/anvil

Length of output: 5888


Narrow the --flavor docs or add support in Swift/Kotlin/JVM. The changelog, roadmap, and todo entries overstate flavor coverage across all six stacks: internal/driver/swift.go and internal/driver/kotlin.go still ignore opts.Flavor. Either thread flavor through those drivers too or limit the docs to the stacks that actually support it.

📍 Affects 3 files
  • CHANGELOG.md#L14-L16 (this comment)
  • docs/ROADMAP.md#L10-L16
  • tasks/todo.md#L41-L44
🤖 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 `@CHANGELOG.md` around lines 14 - 16, Update CHANGELOG.md lines 14-16,
docs/ROADMAP.md lines 10-16, and tasks/todo.md lines 41-44 to limit --flavor
claims to the stacks that currently support it, unless internal/driver/swift.go
and internal/driver/kotlin.go are updated to propagate opts.Flavor through their
build and test steps. Keep the documentation consistent across all three files.

Comment thread docs/ROADMAP.md
Comment on lines +9 to +10
| 1. Detection | Marker-file detectors for Flutter, React Native, Android, iOS, Swift, Kotlin; `anvil detect` | done |
| 2. Guided build | Driver lifecycle (deps, analyze, test, build) for all six stacks; `--flavor`; interactive TUI plus plain fallback; `anvil build` | in progress |

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Keep milestone status consistent across the roadmap and progress board.

docs/ROADMAP.md marks Detection as done, while tasks/todo.md still marks Milestone 1 as in progress. Update one source so contributors do not see conflicting project status.

📍 Affects 2 files
  • docs/ROADMAP.md#L9-L10 (this comment)
  • tasks/todo.md#L39-L49
🤖 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 `@docs/ROADMAP.md` around lines 9 - 10, Synchronize the Detection/Milestone 1
status between docs/ROADMAP.md lines 9-10 and tasks/todo.md lines 39-49 so both
sources use the same completed or in-progress state; update only the
inconsistent status entry and preserve the surrounding roadmap and
progress-board content.

Comment on lines +7 to +32
func (Android) Steps(phase Phase, opts BuildOptions) ([]Step, bool) {
switch phase {
case Deps:
return nil, false
case Analyze:
return []Step{{Name: "gradlew lint", Argv: []string{gradlew(), "lint"}}}, true
case Test:
return []Step{{Name: "gradlew test", Argv: []string{gradlew(), "test"}}}, true
case Build:
task := buildTask(opts)
return []Step{{Name: "gradlew " + task, Argv: []string{gradlew(), task}}}, true
}
return nil, false
}

func buildTask(opts BuildOptions) string {
verb := "assemble"
if opts.Target == "aab" || opts.Target == "appbundle" || opts.Target == "bundle" {
verb = "bundle"
}
variant := "Debug"
if opts.Release {
variant = "Release"
}
return verb + title(opts.Flavor) + variant
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Unrecognized --target values silently fall back to assemble, unlike the Flutter driver.

buildTask only special-cases aab/appbundle/bundle; any other non-empty Target (typo, wrong stack's target value, etc.) silently produces an assemble... task instead of signalling the target is not applicable. Compare with Flutter.Steps (internal/driver/flutter.go lines 17-28), which returns nil, false for any target it doesn't recognize.

🐛 Proposed fix: reject unrecognized non-empty targets
 func (Android) Steps(phase Phase, opts BuildOptions) ([]Step, bool) {
 	switch phase {
 	case Build:
-		task := buildTask(opts)
+		task, ok := buildTask(opts)
+		if !ok {
+			return nil, false
+		}
 		return []Step{{Name: "gradlew " + task, Argv: []string{gradlew(), task}}}, true
 	}
 	...
 }

-func buildTask(opts BuildOptions) string {
+func buildTask(opts BuildOptions) (string, bool) {
 	verb := "assemble"
-	if opts.Target == "aab" || opts.Target == "appbundle" || opts.Target == "bundle" {
+	switch opts.Target {
+	case "", "apk":
+		// verb stays "assemble"
+	case "aab", "appbundle", "bundle":
 		verb = "bundle"
+	default:
+		return "", false
 	}
 	variant := "Debug"
 	if opts.Release {
 		variant = "Release"
 	}
-	return verb + title(opts.Flavor) + variant
+	return verb + title(opts.Flavor) + variant, true
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func (Android) Steps(phase Phase, opts BuildOptions) ([]Step, bool) {
switch phase {
case Deps:
return nil, false
case Analyze:
return []Step{{Name: "gradlew lint", Argv: []string{gradlew(), "lint"}}}, true
case Test:
return []Step{{Name: "gradlew test", Argv: []string{gradlew(), "test"}}}, true
case Build:
task := buildTask(opts)
return []Step{{Name: "gradlew " + task, Argv: []string{gradlew(), task}}}, true
}
return nil, false
}
func buildTask(opts BuildOptions) string {
verb := "assemble"
if opts.Target == "aab" || opts.Target == "appbundle" || opts.Target == "bundle" {
verb = "bundle"
}
variant := "Debug"
if opts.Release {
variant = "Release"
}
return verb + title(opts.Flavor) + variant
}
func (Android) Steps(phase Phase, opts BuildOptions) ([]Step, bool) {
switch phase {
case Deps:
return nil, false
case Analyze:
return []Step{{Name: "gradlew lint", Argv: []string{gradlew(), "lint"}}}, true
case Test:
return []Step{{Name: "gradlew test", Argv: []string{gradlew(), "test"}}}, true
case Build:
task, ok := buildTask(opts)
if !ok {
return nil, false
}
return []Step{{Name: "gradlew " + task, Argv: []string{gradlew(), task}}}, true
}
return nil, false
}
func buildTask(opts BuildOptions) (string, bool) {
verb := "assemble"
switch opts.Target {
case "", "apk":
// verb stays "assemble"
case "aab", "appbundle", "bundle":
verb = "bundle"
default:
return "", false
}
variant := "Debug"
if opts.Release {
variant = "Release"
}
return verb + title(opts.Flavor) + variant, true
}
🤖 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 `@internal/driver/android.go` around lines 7 - 32, Update Android.Steps and
buildTask so non-empty Target values are validated against the supported Android
targets before constructing a Gradle task. Preserve assemble for an empty target
and bundle for aab, appbundle, or bundle; return nil, false from Android.Steps
for any other target, matching Flutter.Steps behavior.

Comment thread internal/driver/driver.go Outdated
Comment on lines +124 to +127
func dirExists(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Remove or use dirExists.

Staticcheck and golangci-lint report this helper as unused, so the required static-analysis check fails.

🧰 Tools
🪛 GitHub Check: staticcheck

[failure] 124-124:
func dirExists is unused (U1000)

🪛 golangci-lint (2.12.2)

[error] 124-124: func dirExists is unused

(unused)

🤖 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 `@internal/driver/driver.go` around lines 124 - 127, Remove the unused
dirExists helper from the driver package, or integrate it into an existing
directory-checking flow if that behavior is required. Ensure the final
implementation has no unused symbol while preserving current functionality.

Source: Linters/SAST tools

Comment thread internal/driver/ios.go
Comment on lines +21 to +30
case Test:
flag, value, scheme := d.container(opts)
if scheme == "" {
return nil, false
}
return []Step{{
Name: "xcodebuild test",
Argv: []string{"xcodebuild", "test", flag, value, "-scheme", scheme,
"-destination", "platform=iOS Simulator,name=iPhone 15"},
}}, true

Copy link
Copy Markdown

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:

Does xcodebuild test -destination "generic/platform=iOS Simulator" work without a specific device name?

💡 Result:

The use of generic/platform=iOS Simulator is generally intended for build actions, not for the test action [1][2][3]. While you can technically use the generic destination to build an Xcode project or scheme without a specific simulator device, the xcodebuild test action requires a concrete execution environment to run the test suite [1][2][3]. Key points regarding this distinction: 1. Building vs. Testing: The generic/ prefix is designed for actions like build or archive, where the goal is to compile the code without necessarily deploying it to a runtime [1][2]. Using generic/platform=iOS Simulator tells xcodebuild that you do not need to target a specific simulator instance for these operations [1][3]. 2. Testing Requirements: When you run the test action, xcodebuild must launch an application (or test bundle) within a simulator runtime [1][3]. Because it needs an active, bootable simulator to perform this, it requires specific criteria—such as a device name (e.g., iPhone 17 Pro) and an OS version (e.g., latest)—to identify and launch the correct simulator [3]. 3. Default Behavior: If you omit the -destination argument entirely, xcodebuild may attempt to select a destination compatible with the selected scheme, but this is often unreliable for automated testing [1][3]. It is standard practice in CI/CD environments to explicitly define the destination using the platform, name, and OS keys (e.g., -destination 'platform=iOS Simulator,name=iPhone 17 Pro,OS=latest') to ensure consistent and reproducible test runs [4][5][3]. In summary, attempting to run xcodebuild test with a generic destination will typically result in an error because the tool lacks the necessary context to initialize the required simulator environment [2][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby references to iPhone 15 / xcodebuild destinations.
git ls-files internal/driver/ios.go README.md docs 2>/dev/null || true

echo '--- internal/driver/ios.go ---'
cat -n internal/driver/ios.go

echo '--- search for iPhone 15 / xcodebuild destination references ---'
rg -n 'iPhone 15|xcodebuild.*destination|generic/platform=iOS Simulator|simctl list devices|OS=latest' . || true

Repository: openforge-oss/anvil

Length of output: 1961


Hardcoded simulator destination makes xcodebuild test brittle.
xcodebuild test needs a concrete, installed simulator; iPhone 15 will fail on Xcode/CI images that don’t have that runtime. Make the destination configurable or pick an available simulator dynamically.

🤖 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 `@internal/driver/ios.go` around lines 21 - 30, Update the Test case in the
driver step construction to avoid hardcoding the iPhone 15 simulator
destination. Make the xcodebuild test destination configurable through the
existing options/configuration flow, or resolve an installed simulator
dynamically, while preserving the current scheme and test arguments.

Comment on lines +32 to +36
if target(opts, "android") == "ios" {
ws := firstGlob(filepath.Join(r.root, "ios"), "*.xcworkspace")
if ws == "" {
return nil, false
}

Copy link
Copy Markdown

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

Resolve the iOS workspace after dependencies run.

pipeline.Plan calls Steps before execution. On a clean checkout, pod install has not yet generated ios/*.xcworkspace, so this returns inapplicable and silently skips the requested iOS build. Defer workspace resolution until the build phase executes, or plan a runtime-resolved Xcode step; add clean-checkout coverage.

🤖 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 `@internal/driver/reactnative.go` around lines 32 - 36, Defer the
ios/*.xcworkspace lookup in the relevant Steps/pipeline planning flow until
execution after dependency installation, rather than returning inapplicable when
firstGlob finds nothing during planning. Ensure the requested iOS build is
retained on a clean checkout and resolve the workspace at build time, with
coverage for this clean-checkout sequence.

davidcreated added a commit that referenced this pull request Jul 23, 2026
* feat(detect): stack detection engine and anvil detect command

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.

* feat(build): guided build lifecycle and anvil build (#3)

* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)

* feat(sign): guided release signing (#11)

Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.
@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
4 of 8 tasks
davidcreated added a commit that referenced this pull request Jul 23, 2026
* feat(detect): stack detection engine and anvil detect command

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.

* feat(build): guided build lifecycle and anvil build (#3)

* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)

* feat(sign): guided release signing (#11)

Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.

* feat(upload): store upload and GoReleaser self-distribution (#13)

Add internal/upload with an Uploader interface and three targets: iOS via xcrun altool (App Store Connect/TestFlight), Android via the Google Play Publisher API (insert edit, upload bundle, assign track, commit) using a service-account JWT, and npm via npm publish. Credentials resolve from flag, env, or a base64 env decoded to a 0600 temp file, and are refused if they live inside the repo. anvil upload is a dry run unless --yes. Add GoReleaser (.goreleaser.yaml) and a tag-triggered release workflow that build cross-platform binaries and publish a GitHub release plus Homebrew cask and Scoop manifests. Tests cover credential resolution, the in-repo refusal, and each uploader's Validate/Describe.

* feat(detect,driver): Go and web/Node stacks (#14)

Add Go and web/Node as detectors and drivers, reusing the existing contract with no new dependencies. Go: go.mod detection (app vs library via a main-package scan); go mod download, go vet + gofmt -l (Analyze classified as failed on non-empty output), go test, go build. Web/Node: framework detection (Next, Nuxt, SvelteKit, Angular, Vite, CRA, Vue, Svelte, Astro, Remix, Gatsby); deps by lockfile (with Yarn Berry --immutable), lint, test (jest/vitest/script with CI=true), build via the package.json script. Monorepo roots (workspaces, pnpm-workspace.yaml, lerna, nx) are descended into rather than claimed, and a single-package Turbo repo is a leaf. Detector order appends Go then Web (Web last, most permissive).
@coderabbitai coderabbitai Bot mentioned this pull request Jul 23, 2026
2 of 8 tasks
davidcreated added a commit that referenced this pull request Jul 24, 2026
* feat(detect): stack detection engine and anvil detect command

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.

* feat(build): guided build lifecycle and anvil build (#3)

* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)

* feat(sign): guided release signing (#11)

Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.

* feat(upload): store upload and GoReleaser self-distribution (#13)

Add internal/upload with an Uploader interface and three targets: iOS via xcrun altool (App Store Connect/TestFlight), Android via the Google Play Publisher API (insert edit, upload bundle, assign track, commit) using a service-account JWT, and npm via npm publish. Credentials resolve from flag, env, or a base64 env decoded to a 0600 temp file, and are refused if they live inside the repo. anvil upload is a dry run unless --yes. Add GoReleaser (.goreleaser.yaml) and a tag-triggered release workflow that build cross-platform binaries and publish a GitHub release plus Homebrew cask and Scoop manifests. Tests cover credential resolution, the in-repo refusal, and each uploader's Validate/Describe.

* feat(detect,driver): Go and web/Node stacks (#14)

Add Go and web/Node as detectors and drivers, reusing the existing contract with no new dependencies. Go: go.mod detection (app vs library via a main-package scan); go mod download, go vet + gofmt -l (Analyze classified as failed on non-empty output), go test, go build. Web/Node: framework detection (Next, Nuxt, SvelteKit, Angular, Vite, CRA, Vue, Svelte, Astro, Remix, Gatsby); deps by lockfile (with Yarn Berry --immutable), lint, test (jest/vitest/script with CI=true), build via the package.json script. Monorepo roots (workspaces, pnpm-workspace.yaml, lerna, nx) are descended into rather than claimed, and a single-package Turbo repo is a leaf. Detector order appends Go then Web (Web last, most permissive).

* chore(release): prepare v0.1.0 (#16)

Rewrite the README for the released tool (eight stacks, full detect/build/sign/upload pipeline, install via binary or go install, usage and flags). Make Homebrew and Scoop upload skip gracefully when HOMEBREW_TAP_TOKEN is absent, so the release ships binaries and the GitHub release without a token and auto-enables tap publishing once the secret is set.
@coderabbitai coderabbitai Bot mentioned this pull request Jul 24, 2026
davidcreated added a commit that referenced this pull request Jul 24, 2026
* feat(detect): stack detection engine and anvil detect command

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.

* feat(build): guided build lifecycle and anvil build (#3)

* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)

* feat(sign): guided release signing (#11)

Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.

* feat(upload): store upload and GoReleaser self-distribution (#13)

Add internal/upload with an Uploader interface and three targets: iOS via xcrun altool (App Store Connect/TestFlight), Android via the Google Play Publisher API (insert edit, upload bundle, assign track, commit) using a service-account JWT, and npm via npm publish. Credentials resolve from flag, env, or a base64 env decoded to a 0600 temp file, and are refused if they live inside the repo. anvil upload is a dry run unless --yes. Add GoReleaser (.goreleaser.yaml) and a tag-triggered release workflow that build cross-platform binaries and publish a GitHub release plus Homebrew cask and Scoop manifests. Tests cover credential resolution, the in-repo refusal, and each uploader's Validate/Describe.

* feat(detect,driver): Go and web/Node stacks (#14)

Add Go and web/Node as detectors and drivers, reusing the existing contract with no new dependencies. Go: go.mod detection (app vs library via a main-package scan); go mod download, go vet + gofmt -l (Analyze classified as failed on non-empty output), go test, go build. Web/Node: framework detection (Next, Nuxt, SvelteKit, Angular, Vite, CRA, Vue, Svelte, Astro, Remix, Gatsby); deps by lockfile (with Yarn Berry --immutable), lint, test (jest/vitest/script with CI=true), build via the package.json script. Monorepo roots (workspaces, pnpm-workspace.yaml, lerna, nx) are descended into rather than claimed, and a single-package Turbo repo is a leaf. Detector order appends Go then Web (Web last, most permissive).

* chore(release): prepare v0.1.0 (#16)

Rewrite the README for the released tool (eight stacks, full detect/build/sign/upload pipeline, install via binary or go install, usage and flags). Make Homebrew and Scoop upload skip gracefully when HOMEBREW_TAP_TOKEN is absent, so the release ships binaries and the GitHub release without a token and auto-enables tap publishing once the secret is set.

* docs: real install instructions, and bump actions off Node 20 (#18)

README now shows the working Homebrew cask and Scoop bucket commands published by the v0.1.0 release, and notes that go install lands in GOPATH/bin. Mark 0.1.0 as released in the changelog instead of leaving shipped work under Unreleased. Bump actions/checkout to v7 and actions/setup-go to v7 across CI and release, clearing the Node 20 deprecation warning on the release run.
davidcreated added a commit that referenced this pull request Aug 25, 2026
)

* feat(detect): stack detection engine and anvil detect command

Add internal/detect with marker-file detectors for Flutter (vs pure Dart; app/module/plugin), React Native (bare, Expo managed, Expo prebuild), native Android (app vs library, KMP flag), and native iOS (Xcode/SPM/Podfile). A depth-limited prune-on-detect scanner with skip lists and a containment sweep attributes android/ios folders to their Flutter or RN parent and surfaces each monorepo project once. Add the Cobra-based anvil detect command (table and --json, with --path and --depth). 16 fixture-tree tests cover the edge cases (node_modules exclusion, monorepo, KMP, plugin example pruning). Also apply the no-emojis, no-dash-connectors, minimal-comments style across docs and record it in CLAUDE.md.

* feat(build): guided build lifecycle and anvil build (#3)

* feat(build): guided build lifecycle and anvil build

Add the driver contract (internal/driver) and drivers for Flutter, React Native, native Android, native iOS, Swift (SPM), and Kotlin/JVM, with --flavor threaded into build and test steps. Add the runner (internal/pipeline) that streams combined output, captures exit codes, classifies results, collects artifacts, and fail-fasts. Add internal/tui with a Bubble Tea view and a plain non-TTY renderer, and the anvil build command (--path/--target/--flavor/--release/--dry-run/--plain). Refine detection so Gradle is not always Android and Package.swift is Swift, adding Swift and Kotlin stacks. Tests cover driver steps, the runner via a real subprocess, and the plain renderer.

* fix: remove unused dirExists helper (staticcheck U1000)

* feat(sign): guided release signing (#11)

Add a Sign phase and the anvil sign command plus anvil build --sign. internal/sign generates a PKCS12 keystore with keytool, writes key.properties, wires Gradle signingConfigs, writes an iOS ExportOptions.plist, and gitignores the secrets. iOS sign steps live on the Flutter (flutter build ipa), React Native, and native iOS (xcodebuild archive + exportArchive) drivers; Android signs at build time via the wired Gradle config. Passwords come from prompts (huh) or environment, never the repo, and --dry-run makes no changes. Tests cover live keystore generation (keytool), Gradle wiring idempotence, gitignore, ExportOptions, and step argv.

* feat(upload): store upload and GoReleaser self-distribution (#13)

Add internal/upload with an Uploader interface and three targets: iOS via xcrun altool (App Store Connect/TestFlight), Android via the Google Play Publisher API (insert edit, upload bundle, assign track, commit) using a service-account JWT, and npm via npm publish. Credentials resolve from flag, env, or a base64 env decoded to a 0600 temp file, and are refused if they live inside the repo. anvil upload is a dry run unless --yes. Add GoReleaser (.goreleaser.yaml) and a tag-triggered release workflow that build cross-platform binaries and publish a GitHub release plus Homebrew cask and Scoop manifests. Tests cover credential resolution, the in-repo refusal, and each uploader's Validate/Describe.

* feat(detect,driver): Go and web/Node stacks (#14)

Add Go and web/Node as detectors and drivers, reusing the existing contract with no new dependencies. Go: go.mod detection (app vs library via a main-package scan); go mod download, go vet + gofmt -l (Analyze classified as failed on non-empty output), go test, go build. Web/Node: framework detection (Next, Nuxt, SvelteKit, Angular, Vite, CRA, Vue, Svelte, Astro, Remix, Gatsby); deps by lockfile (with Yarn Berry --immutable), lint, test (jest/vitest/script with CI=true), build via the package.json script. Monorepo roots (workspaces, pnpm-workspace.yaml, lerna, nx) are descended into rather than claimed, and a single-package Turbo repo is a leaf. Detector order appends Go then Web (Web last, most permissive).

* chore(release): prepare v0.1.0 (#16)

Rewrite the README for the released tool (eight stacks, full detect/build/sign/upload pipeline, install via binary or go install, usage and flags). Make Homebrew and Scoop upload skip gracefully when HOMEBREW_TAP_TOKEN is absent, so the release ships binaries and the GitHub release without a token and auto-enables tap publishing once the secret is set.

* docs: real install instructions, and bump actions off Node 20 (#18)

README now shows the working Homebrew cask and Scoop bucket commands published by the v0.1.0 release, and notes that go install lands in GOPATH/bin. Mark 0.1.0 as released in the changelog instead of leaving shipped work under Unreleased. Bump actions/checkout to v7 and actions/setup-go to v7 across CI and release, clearing the Node 20 deprecation warning on the release run.

* feat(build): add lifecycle phase selection (#20)

* feat(build): add lifecycle phase selection

* docs: record phase selection verification

---------

Co-authored-by: Trần Đình Huy <huytdps13400@fpt.edu.vn>
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