Skip to content

Conversation

tusharmath
Copy link
Contributor

@tusharmath tusharmath commented Sep 2, 2025

🔄 Refactor: Improve API Ergonomics with Method Chaining

📝 Summary

This PR refactors the method signatures across the gh-workflow crate to improve the developer experience by enabling fluent method chaining. The changes convert setter methods from taking &mut self to taking mut self and returning Self, allowing for more idiomatic and readable code.

🎯 Motivation

The previous API design required multiple mutable variable bindings when building workflows, jobs, and other configurations. This made the code verbose and less ergonomic. By adopting the builder pattern with method chaining, users can now write more concise and expressive code.

🔧 Key Changes

Method Signature Updates

  • Changed setter methods from &mut self -> () to mut self -> Self across multiple structs:
    • Workflow struct methods (add_job, add_event, add_env)
    • Job struct methods (add_step, add_env, add_needs)
    • Jobs collection method (insert renamed to add)
    • Cargo configuration methods
    • Event configuration methods

API Simplifications

  • Removed conditional methods: Eliminated *_when variants (add_job_when, add_step_when, add_env_when) as they can now be handled with standard Rust control flow
  • Removed redundant methods:
    • Dropped add_steps method from Job struct (users can chain multiple add_step calls)
    • Removed get_id reverse lookup method from Workflow
  • Simplified job dependencies: Removed temporary tmp_needs field and complex dependency resolution logic

Implementation Improvements

  • Updated internal implementations to use take() for cleaner Option handling
  • Streamlined the generate.rs module by removing ~110 lines of complex dependency management code

📊 Impact

Before (verbose, multiple mutable bindings):

let mut workflow = Workflow::new("CI");
workflow.add_event(Event::default());
let mut job = Job::new();
job.add_step(step1);
job.add_step(step2);
workflow.add_job("test", job);

After (fluent, chainable):

let workflow = Workflow::new("CI")
    .add_event(Event::default())
    .add_job("test", Job::new()
        .add_step(step1)
        .add_step(step2));

✅ Benefits

  • Improved Ergonomics: More idiomatic Rust code with builder pattern
  • Reduced Boilerplate: Eliminates the need for mutable variable bindings
  • Better Readability: Workflow definitions are more declarative and easier to understand
  • Simplified Codebase: Removed ~120+ lines of complex logic and redundant methods
  • Type Safety: Maintains full type safety while improving usability

🧪 Testing

  • All existing tests pass
  • Updated test files to use the new chaining API
  • Verified compatibility with gh-workflow-tailcall integration

📦 Affected Crates

  • gh-workflow: Core library refactoring
  • gh-workflow-tailcall: Updated to use new API patterns

⚠️ Breaking Changes

This is a breaking change as it modifies public API signatures. Users will need to update their code to use the new chaining pattern. However, the migration is straightforward and improves code quality.

🔍 Files Changed

  • crates/gh-workflow/src/workflow.rs - Core workflow building APIs
  • crates/gh-workflow/src/cargo.rs - Cargo configuration methods
  • crates/gh-workflow/src/event.rs - Event configuration methods
  • crates/gh-workflow/src/generate.rs - Simplified generation logic
  • crates/gh-workflow-tailcall/src/standard.rs - Updated integration
  • crates/gh-workflow-tailcall/tests/ci.rs - Test updates

📚 Migration Guide

Users should update their code to chain methods instead of using mutable references:

  1. Remove mut from workflow/job variable declarations
  2. Chain method calls using the builder pattern
  3. Remove any usage of deprecated *_when methods
  4. Update any code using add_steps to chain multiple add_step calls

🚀 Next Steps

  • Update documentation with new usage examples
  • Consider adding a migration script for large codebases
  • Publish new major version due to breaking changes

Summary by CodeRabbit

  • New Features
    • Expanded workflow modeling: artifacts, permissions, environment, container, concurrency, defaults, strategy, secrets, expressions, richer steps/jobs and builder-style composition; improved CI lint, autofix, toolchain, caching, Nextest and benchmark support.
  • Breaking Changes
    • Mutation/API patterns changed to more in-place/builder styles; several conditional helper APIs removed.
  • Refactor
    • Public API surface reorganized and flattened with types moved into dedicated modules and new re-exports.

Copy link

coderabbitai bot commented Sep 2, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Restructures gh-workflow into focused modules, converts many builder APIs to in-place mutation, adds typed workflow primitives (artifacts, concurrency, container, env, etc.), reworks Tailcall CI generation (incremental assembly, revamped lint/test/release jobs, StandardWorkflow::add_setup), and removes dependency-organizing helpers.

Changes

Cohort / File(s) Summary
Core workflow refactor
crates/gh-workflow/src/workflow.rs, crates/gh-workflow/src/lib.rs
Splits large workflow types into modules, changes mutation semantics to &mut/borrow_self and take(), replaces Jobs::insert with add, exposes getters/adders, and re-exports new modules from crate root.
Job & step model
crates/gh-workflow/src/job.rs, crates/gh-workflow/src/step.rs
Adds RunsOn and Job, typed Step/StepValue/StepType (Run/Use/Toolchain), Input helper, builder-style APIs (add_step, add_env, add_needs), and serialization-ready step modeling.
New primitive modules
crates/gh-workflow/src/{artifacts.rs,concurrency.rs,container.rs,defaults.rs,env.rs,environment.rs,expression.rs,permissions.rs,secret.rs,strategy.rs}
Introduces public types for artifacts, concurrency, container (Volume::new), defaults, env (IndexMap wrapper), environment, expression (transparent), permissions (Level enum), secret, and strategy; all derive serde and Setters with #[setters(strip_option, into)].
Cargo command changes
crates/gh-workflow/src/cargo.rs
Removes public add_args_when; From→Step now clones command, and sets step label to Cargo {command} (Step::new(...).run(...)).
Generator simplification
crates/gh-workflow/src/generate.rs
Setter derives updated to strip_option; removed organize_job_dependency logic and related tests; Generate::new no longer reorganizes dependencies.
Tailcall CI generator
crates/gh-workflow-tailcall/src/standard.rs
Adds #[setters(strip_option, into)] on StandardWorkflow and add_setup(...); to_ci_workflow builds workflow incrementally, reworks lint_job (autofix vs check branches, shared nightly toolchain), reworks test_job (rust-cache, nextest install, cargo vs nextest test, optional bench), and simplifies release job needs to explicit ["build","lint"].
Tailcall tests
crates/gh-workflow-tailcall/tests/ci.rs
Adds to_owned() in builder chain before generate() to satisfy ownership changes.
Toolchain & release adjustments
crates/gh-workflow/src/toolchain.rs, crates/gh-workflow/src/release_plz.rs
Renamed Toolchain.field to version, added setter delegate generation, updated From and From Step construction to use Step::new(...).uses(...).
Workflow primitives wiring
crates/gh-workflow/src/{container.rs,env.rs,artifacts.rs,concurrency.rs,defaults.rs,environment.rs,expression.rs,permissions.rs,secret.rs,strategy.rs}
New files added modeling various GH workflow primitives with serde and setters (see New primitive modules).
CI workflow pinning
.github/workflows/autofix.yml
Replaced an action SHA with a named step and version tag: name: auto-fix and uses: autofix-ci/action@v1.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Caller
    participant Std as StandardWorkflow
    participant Builder as Workflow (mutable)
    participant Jobs
    participant Lint
    participant Test
    participant Release

    Caller->>Std: to_ci_workflow(opts)
    Std->>Builder: create base workflow (mutable)
    Builder->>Jobs: add lint job
    Jobs->>Lint: assemble nightly toolchain + fmt/clippy steps
    alt auto_fix
        Lint->>Lint: fmt (nightly), clippy --fix --allow-dirty, add autofix-ci step
    else
        Lint->>Lint: fmt --check, clippy -D warnings
    end
    Builder->>Jobs: add test job
    Jobs->>Test: add rust-cache, optional nextest install, run tests (cargo/nextest), optional bench
    alt auto_release
        Builder->>Jobs: add release & release-pr jobs (needs: "build","lint")
    end
    Std-->>Caller: return assembled Workflow
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

I hop through structs and serde hues,
I split and stitch the workflow views.
I add a setup, reorder steps with care,
Lint, test, release — I nudge them there.
A rabbit builder, tidy and spry 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 7f612be and d707126.

📒 Files selected for processing (4)
  • .github/workflows/autofix.yml (1 hunks)
  • crates/gh-workflow-tailcall/src/standard.rs (5 hunks)
  • crates/gh-workflow/src/step.rs (1 hunks)
  • crates/gh-workflow/src/toolchain.rs (6 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mut-self

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

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

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (2)
crates/gh-workflow/src/workflow.rs (1)

409-414: StepValue setters still consume self — add borrow_self for consistency.

All other builders now return &mut Self; StepValue remains by-value. This makes delegated setters on Step/Step consume the step, breaking uniform chaining across the API.

Apply:

 #[setters(
     strip_option,
     into,
+    borrow_self,
     generate_delegates(ty = "Step<Run>", field = "value"),
     generate_delegates(ty = "Step<Use>", field = "value")
 )]
crates/gh-workflow/src/event.rs (1)

103-109: Return type inconsistency: methods mutate self but return &Self, breaking chaining with &mut setters.

These functions accept &mut self and push into Vecs but return &Self. That prevents chaining with newly generated setters that require &mut Self.

Apply this mechanical change pattern (example shown; repeat for all listed methods):

-pub fn add_type(&mut self, type_: BranchProtectionRuleType) -> &Self {
+pub fn add_type(&mut self, type_: BranchProtectionRuleType) -> &mut Self {
     self.types.push(type_);
     self
}

Similarly for:

  • CheckRun::add_type
  • CheckSuite::add_type
  • Create::{add_branch, add_tag}
  • Delete::{add_branch, add_tag}
  • Deployment::add_branch
  • DeploymentStatus::add_state
  • Discussion::add_type
  • DiscussionComment::add_type
  • IssueComment::add_type
  • Issues::add_type
  • Label::add_type
  • MergeGroup::add_type
  • Milestone::add_type
  • PullRequestTarget::{add_type, add_branch}
  • Push::{add_branch, add_path, add_tag} – already returning &mut Self (keep as-is)
  • RepositoryDispatch::add_type
  • Schedule::add_cron
  • Watch::add_type
  • WorkflowRun::{add_type, add_workflow, add_branch}


Also applies to: 135-139, 161-165, 182-186, 188-192, 209-213, 215-219, 233-238, 253-257, 303-307, 333-337, 362-366, 419-423, 450-454, 470-474, 494-498, 531-535, 536-540, 541-545, 572-576, 602-606, 623-627, 629-633, 653-657, 659-663, 665-669, 693-698, 733-737, 747-751, 761-765, 775-779, 898-902, 904-908, 910-914

</blockquote></details>

</blockquote></details>
🧹 Nitpick comments (8)
crates/gh-workflow/src/cargo.rs (3)

46-49: args() setter overwrites to a single token — consider clarifying or adding a multi-arg API.

Current doc says “overwritten”; implementation stores a single element. Recommend adding explicit single-token and multi-token helpers to avoid ambiguity.

Apply within-file diff to keep behavior but delegate to token helpers:

 pub fn args<T: ToString>(&mut self, args: T) -> &mut Self {
-    self.args = vec![args.to_string()];
+    self.args = vec![args.to_string()];
     self
 }

Additions outside the shown range (new helpers):

impl Cargo {
    pub fn add_arg<S: ToString>(&mut self, arg: S) -> &mut Self {
        self.args.push(arg.to_string());
        self
    }

    pub fn add_args_tokens<I, S>(&mut self, args: I) -> &mut Self
    where
        I: IntoIterator<Item = S>,
        S: ToString,
    {
        self.args.extend(args.into_iter().map(|s| s.to_string()));
        self
    }
}

52-60: Avoid split_whitespace tokenization; accept an iterator of args instead.

Whitespace-splitting breaks quoted values and flags with spaces. Keep this method for back-compat, but add an iterator-based variant and prefer it in new code.

Apply this focused change to delegate split_whitespace to a token-aware helper:

 pub fn add_args<T: ToString>(&mut self, args: T) -> &mut Self {
-    self.args.extend(
-        args.to_string()
-            .split_whitespace()
-            .map(|s| s.to_string())
-            .collect::<Vec<_>>(),
-    );
+    // Back-compat: keep simple whitespace split, but route through token API.
+    let tokens = args.to_string().split_whitespace().map(|s| s.to_string());
+    self.add_args_tokens(tokens);
     self
 }

63-81: Optional: quote/escape args when joining into a shell string.

Join with proper shell escaping to avoid breakage with spaces/special chars. Consider shell-escape for POSIX shells; on Windows, respect configured shell.

crates/gh-workflow/src/workflow.rs (1)

525-528: Optional security hardening: pin checkout to a commit SHA.

actions/checkout v5 exists as of August 2025, but pinning to the v5.0.0 commit (08c6903cd8c0fde910a37f88322edcfb5dd907a8) mitigates tag-retargeting risks. Consider:

  • keep API taking a “version” string,
  • change checkout() to pass the SHA instead of the tag.

Example update outside the shown range:

 pub fn checkout() -> Step<Use> {
-    Step::uses("actions", "checkout", "v5").name("Checkout Code")
+    Step::uses("actions", "checkout", "08c6903cd8c0fde910a37f88322edcfb5dd907a8")
+        .name("Checkout Code")
 }

References: actions/checkout v5.0.0 exists and the SHA above corresponds to that release; pinning is widely recommended for supply-chain safety. (forge.tourmentine.com, emmer.dev)

crates/gh-workflow-tailcall/tests/ci.rs (1)

8-8: Avoid cloning in the test chain; use a local mutable instead.

to_owned() clones the workflow just to call generate(self). Slightly cleaner (and avoids the clone):

 fn generate() {
-    StandardWorkflow::default()
-        .auto_release(true)
-        .auto_fix(true)
-        .to_owned()
-        .generate()
-        .unwrap();
+    let mut wf = StandardWorkflow::default();
+    wf.auto_release(true).auto_fix(true);
+    wf.generate().unwrap();
 }
crates/gh-workflow-tailcall/src/standard.rs (3)

174-189: Prefer consistent args API usage for readability.

You mix args(...) and add_args(...). Consider using one style (e.g., repeated add_args with single tokens) for clarity and safer quoting.

Example:

-        clippy_step.add_args("--all-features --workspace -- -D warnings");
+        clippy_step
+            .add_args("--all-features")
+            .add_args("--workspace")
+            .add_args("--")
+            .add_args("-D")
+            .add_args("warnings");

206-212: Pin looks good; consider adding a brief comment for the pinned SHA.

Optional: add a note with the action version/tag mapped to this SHA for future upgrades.

             job.add_step(Step::uses(
                 "autofix-ci",
                 "action",
                 "551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef",
             ));
+            // Pinned to action vX.Y.Z (update SHA alongside version bump)

114-114: Typo in comment.

“Nneeds to by” → “needs to be”.

-        // NOTE: The workflow name needs to by `autofix.ci`
+        // NOTE: The workflow name needs to be `autofix.ci`
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e2c0493 and 4c60814.

📒 Files selected for processing (6)
  • crates/gh-workflow-tailcall/src/standard.rs (5 hunks)
  • crates/gh-workflow-tailcall/tests/ci.rs (1 hunks)
  • crates/gh-workflow/src/cargo.rs (2 hunks)
  • crates/gh-workflow/src/event.rs (43 hunks)
  • crates/gh-workflow/src/generate.rs (3 hunks)
  • crates/gh-workflow/src/workflow.rs (22 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • crates/gh-workflow/src/generate.rs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.
📚 Learning: 2024-12-10T06:33:21.046Z
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.

Applied to files:

  • crates/gh-workflow-tailcall/tests/ci.rs
  • crates/gh-workflow-tailcall/src/standard.rs
  • crates/gh-workflow/src/workflow.rs
🧬 Code graph analysis (1)
crates/gh-workflow-tailcall/src/standard.rs (2)
crates/gh-workflow/src/cargo.rs (1)
  • new (28-36)
crates/gh-workflow/src/workflow.rs (10)
  • new (108-110)
  • new (233-239)
  • new (362-364)
  • new (645-655)
  • new (676-679)
  • new (816-818)
  • checkout (526-528)
  • github (352-359)
  • uses (474-488)
  • uses (514-523)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test
🔇 Additional comments (17)
crates/gh-workflow/src/cargo.rs (3)

7-7: Setter ergonomics: borrow_self adoption looks good.

Consistent with the PR’s shift to in-place mutation and fluent chaining.


39-42: nightly() signature change is correct and chain-friendly.

No functional concerns.


26-36: add_args_when removal verified across repo — no occurrences found.

crates/gh-workflow/src/workflow.rs (11)

46-47: borrow_self on Workflow setters — good move.

Improves ergonomics without changing serialization.


117-126: add_job uses take() correctly to avoid moves.

Solid pattern for in-place mutation.


139-147: add_event merge logic is sound.

Merges when present; initializes otherwise.


150-156: add_env uses take+extend — good.

Preserves existing vars and avoids Option juggling.


242-249: add_step API: nice use of StepType and take().

No issues.


252-258: Job::add_env mirrors Workflow::add_env — consistent.

Looks good.


265-272: add_steps loops via add_step — fine, preserves validation.

No concerns.


274-281: add_needs accumulates tmp_needs — consistent with builder staging.

Assuming later resolution to string IDs remains unchanged.


495-500: Step::add_env switch to take()+extend is correct.

Matches the add_env pattern elsewhere.


531-541: Step::add_with uses take()+merge and emptiness pruning — good.

Keeps YAML clean by omitting empty maps.


579-606: Bulk borrow_self migration across Container/Credentials/Volume/... — LGTM.

Consistent, no semantic changes.

Also applies to: 612-619, 635-642, 662-673, 686-727, 729-736, 742-754, 760-767, 773-785, 791-799, 825-832, 847-855, 861-871

crates/gh-workflow/src/event.rs (1)

14-15: borrow_self derivations across events — good consistency.

Matches the project-wide API direction.

Also applies to: 97-98, 127-128, 153-154, 170-171, 197-198, 225-226, 244-245, 441-442, 463-464, 487-488, 520-521, 562-564, 593-594, 611-612, 638-639, 685-686, 724-725, 739-741, 754-755, 768-769, 784-785, 799-800, 817-818, 829-830, 843-844, 852-853, 883-884

crates/gh-workflow-tailcall/src/standard.rs (2)

29-30: Setter borrow_self change: confirm downstream call sites are updated.

Switching setters to borrow &mut self changes chaining semantics. CI passed here, but please ensure dependent crates don’t rely on by-value setters.


219-233: Nice addition: rust-cache to speed up CI.

Good call on caching; aligns with typical Rust CI best practices.

Comment on lines 112 to 123
/// Converts the workflow into a Github workflow.
fn to_autofix_workflow(&self) -> GHWorkflow {
// NOTE: The workflow name needs to by `autofix.ci`
GHWorkflow::new("autofix.ci")
let mut workflow = GHWorkflow::new("autofix.ci");
workflow
.add_env(self.workflow_flags())
.on(self.workflow_event())
.add_job("lint", self.lint_job(true))
.add_job("lint", self.lint_job(true));

workflow
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Autofix workflow triggers on pushes; restrict to PRs only.

Objective requires auto-commit to run only on PRs. to_autofix_workflow() currently uses the same event as CI (includes pushes), which can push to main.

Restrict events here (leaving CI events unchanged):

-        workflow
-            .add_env(self.workflow_flags())
-            .on(self.workflow_event())
-            .add_job("lint", self.lint_job(true));
+        workflow
+            .add_env(self.workflow_flags())
+            .on(
+                Event::default()
+                    .pull_request(
+                        PullRequest::default()
+                            .add_type(PullRequestType::Opened)
+                            .add_type(PullRequestType::Synchronize)
+                            .add_type(PullRequestType::Reopened)
+                            .add_branch("main")
+                            .to_owned(),
+                    )
+                    .to_owned(),
+            )
+            .add_job("lint", self.lint_job(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
/// Converts the workflow into a Github workflow.
fn to_autofix_workflow(&self) -> GHWorkflow {
// NOTE: The workflow name needs to by `autofix.ci`
GHWorkflow::new("autofix.ci")
let mut workflow = GHWorkflow::new("autofix.ci");
workflow
.add_env(self.workflow_flags())
.on(self.workflow_event())
.add_job("lint", self.lint_job(true))
.add_job("lint", self.lint_job(true));
workflow
}
/// Converts the workflow into a Github workflow.
fn to_autofix_workflow(&self) -> GHWorkflow {
// NOTE: The workflow name needs to by `autofix.ci`
let mut workflow = GHWorkflow::new("autofix.ci");
workflow
.add_env(self.workflow_flags())
.on(
Event::default()
.pull_request(
PullRequest::default()
.add_type(PullRequestType::Opened)
.add_type(PullRequestType::Synchronize)
.add_type(PullRequestType::Reopened)
.add_branch("main")
.to_owned(),
)
.to_owned(),
)
.add_job("lint", self.lint_job(true));
workflow
}
🤖 Prompt for AI Agents
In crates/gh-workflow-tailcall/src/standard.rs around lines 112 to 123, the
autofix workflow currently uses self.workflow_event() (which includes pushes)
and must be restricted to PRs only; change the workflow trigger to a
pull_request-only event (e.g., set the workflow to trigger on pull_request with
types like opened, reopened, synchronize) instead of reusing
self.workflow_event(), leaving the CI workflow code unchanged.

Comment on lines 163 to 173
fn lint_job(&self, auto_fix: bool) -> Job {
let job = self.init_job(if auto_fix { "Lint Fix" } else { "Lint" });
let mut job = self.init_job(if auto_fix { "Lint Fix" } else { "Lint" });

let job = if auto_fix {
if auto_fix {
job.concurrency(
Concurrency::new(Expression::new("autofix-${{github.ref}}"))
.cancel_in_progress(false),
)
} else {
job
};
.cancel_in_progress(false)
.to_owned(),
);
}

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Autofix job lacks write permissions to push commits.

init_job() sets contents: read. The autofix job needs contents: write (and typically pull_requests: write) to push fixes.

Grant write only when auto_fix is true:

-        if auto_fix {
+        if auto_fix {
+            job.permissions(self.write_permissions());
             job.concurrency(
                 Concurrency::new(Expression::new("autofix-${{github.ref}}"))
                     .cancel_in_progress(false)
                     .to_owned(),
             );
         }
📝 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
fn lint_job(&self, auto_fix: bool) -> Job {
let job = self.init_job(if auto_fix { "Lint Fix" } else { "Lint" });
let mut job = self.init_job(if auto_fix { "Lint Fix" } else { "Lint" });
let job = if auto_fix {
if auto_fix {
job.concurrency(
Concurrency::new(Expression::new("autofix-${{github.ref}}"))
.cancel_in_progress(false),
)
} else {
job
};
.cancel_in_progress(false)
.to_owned(),
);
}
fn lint_job(&self, auto_fix: bool) -> Job {
let mut job = self.init_job(if auto_fix { "Lint Fix" } else { "Lint" });
if auto_fix {
job.permissions(self.write_permissions());
job.concurrency(
Concurrency::new(Expression::new("autofix-${{github.ref}}"))
.cancel_in_progress(false)
.to_owned(),
);
}
🤖 Prompt for AI Agents
In crates/gh-workflow-tailcall/src/standard.rs around lines 163 to 173, the
autofix branch uses a job initialized with read-only permissions (init_job sets
contents: read) but needs write permissions to push commits; when auto_fix is
true update the job permissions to set contents: write (and typically
pull_requests: write) only in that branch — e.g. after creating job for autofix
call the job permissions setter to change contents to write and add
pull_requests write, leaving the non-autofix path with the original read-only
permissions.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
crates/gh-workflow-tailcall/src/standard.rs (3)

77-83: Setup steps run in reverse order; use FIFO.

Users expect insertion order. Also fix the misleading comment.

Apply:

-        // Add setup steps in reverse order to maintain the correct sequence
-        for step in self.setup.iter().rev() {
+        // Add setup steps in insertion order
+        for step in &self.setup {
             job.add_step(step.clone());
         }

115-121: Autofix workflow must trigger on PRs only (not pushes).

Per objective, auto-commit must never run on main. Restrict triggers here (leave CI workflow unchanged).

Apply:

-        workflow
-            .add_env(self.workflow_flags())
-            .on(self.workflow_event())
-            .add_job("lint", self.lint_job(true));
+        workflow
+            .add_env(self.workflow_flags())
+            .on(
+                Event::default()
+                    .pull_request(
+                        PullRequest::default()
+                            .add_type(PullRequestType::Opened)
+                            .add_type(PullRequestType::Synchronize)
+                            .add_type(PullRequestType::Reopened)
+                            .add_branch("main")
+                            .to_owned(),
+                    )
+                    .to_owned(),
+            )
+            .add_job("lint", self.lint_job(true));

163-173: Autofix job needs write permissions to push.

init_job sets contents: read. Grant write only when auto_fix is true.

Apply:

-        if auto_fix {
-            job.concurrency(
+        if auto_fix {
+            job
+                .permissions(self.write_permissions())
+                .concurrency(
                 Concurrency::new(Expression::new("autofix-${{github.ref}}"))
                     .cancel_in_progress(false)
                     .to_owned(),
-            );
+                );
         }
🧹 Nitpick comments (1)
crates/gh-workflow/src/generate.rs (1)

39-51: Prefer GITHUB_WORKSPACE; fall back to git rev-parse.

Avoids spawning git when the env is present (faster, works in CI/worktrees).

Apply:

-        let root_dir = String::from_utf8(
-            Command::new("git")
-                .args(["rev-parse", "--show-toplevel"])
-                .output()?
-                .stdout,
-        )?;
+        let root_dir = if let Ok(ws) = std::env::var("GITHUB_WORKSPACE") {
+            ws
+        } else {
+            String::from_utf8(
+                Command::new("git")
+                    .args(["rev-parse", "--show-toplevel"])
+                    .output()?
+                    .stdout,
+            )?
+        };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4c60814 and f22b754.

📒 Files selected for processing (3)
  • crates/gh-workflow-tailcall/src/standard.rs (5 hunks)
  • crates/gh-workflow/src/generate.rs (1 hunks)
  • crates/gh-workflow/src/workflow.rs (23 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.
📚 Learning: 2024-12-10T06:33:21.046Z
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.

Applied to files:

  • crates/gh-workflow-tailcall/src/standard.rs
  • crates/gh-workflow/src/workflow.rs
🧬 Code graph analysis (1)
crates/gh-workflow-tailcall/src/standard.rs (2)
crates/gh-workflow/src/workflow.rs (10)
  • new (108-110)
  • new (230-236)
  • new (360-362)
  • new (643-653)
  • new (674-677)
  • new (815-817)
  • checkout (524-526)
  • github (350-357)
  • uses (472-486)
  • uses (512-521)
crates/gh-workflow/src/cargo.rs (1)
  • new (28-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test
🔇 Additional comments (9)
crates/gh-workflow-tailcall/src/standard.rs (6)

29-29: Setter mutability on StandardWorkflow — LGTM.

Adopting borrow_self improves ergonomics and matches the project-wide builder pattern.


142-161: Release job dependencies by ID — LGTM.

Using .add_needs("build") and .add_needs("lint") resolves the earlier mismatch risk.


174-189: Lint steps composition — LGTM.

Nightly fmt/clippy setup and --check gating look correct; warnings are denied.

Also applies to: 200-205


206-213: Action pinned by commit — good supply-chain hygiene.


218-255: Build/Test job — LGTM.

Nextest install is gated; rust-cache v2 with cache-all-crates enabled looks good.


257-263: Permissions and events helpers — LGTM.

write_permissions returns an owned value; workflow_event covers push main + tags and PRs targeting main.

Also applies to: 272-284

crates/gh-workflow/src/generate.rs (1)

13-14: Setter mutability on Generate — LGTM.

crates/gh-workflow/src/workflow.rs (2)

46-47: Borrow-self migration and in-place mutation — LGTM across these sections.

Consistent &mut chaining, Option::take patterns, and transparent Expression/Copy Level are well applied.

Also applies to: 118-126, 139-147, 150-156, 186-190, 239-246, 249-256, 261-269, 271-278, 492-498, 529-540, 659-660, 683-684, 739-740, 757-758, 771-771, 808-811, 727-734


106-116: No direct Workflow::new(...).<builder> chains found. Also verify no .default().<builder> chains on temporaries.

@tusharmath tusharmath changed the title mut self refactor: improve API ergonomics with builder pattern and method chaining Sep 2, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (3)
crates/gh-workflow-tailcall/src/standard.rs (3)

75-78: Setup steps are added in reverse order, contradicting expected FIFO behavior.

The comment says "in reverse order to maintain the correct sequence" but this is counterintuitive. Users expect setup steps to execute in the order they were added (FIFO). The current implementation reverses this, which could lead to unexpected behavior.

Apply this diff to fix the order:

-        // Add setup steps in reverse order to maintain the correct sequence
-        for step in self.setup.iter().rev() {
+        // Add setup steps in insertion order
+        for step in &self.setup {
             job = job.add_step(step.clone());
         }

111-114: Autofix workflow triggers on pushes; restrict to PRs only.

The autofix workflow uses the same event configuration as CI (line 113: self.workflow_event()), which includes pushes to main. Per the PR objectives, auto-commit should only run on pull requests to prevent pushing to main.

Apply this diff to restrict autofix to pull requests only:

         GHWorkflow::new("autofix.ci")
             .add_env(self.workflow_flags())
-            .on(self.workflow_event())
+            .on(
+                Event::default()
+                    .pull_request(
+                        PullRequest::default()
+                            .add_type(PullRequestType::Opened)
+                            .add_type(PullRequestType::Synchronize)
+                            .add_type(PullRequestType::Reopened)
+                            .add_branch("main"),
+                    ),
+            )
             .add_job("lint", self.lint_job(true))

155-160: Autofix job lacks write permissions to push commits.

The autofix job is initialized with read-only permissions from init_job() (line 73 sets contents: read). However, to push lint fixes back to the PR branch, it needs write permissions.

Add write permissions when auto_fix is true:

         if auto_fix {
+            job = job.permissions(self.write_permissions());
             job = job.concurrency(
                 Concurrency::new(Expression::new("autofix-${{github.ref}}"))
                     .cancel_in_progress(false),
             );
         }
🧹 Nitpick comments (1)
crates/gh-workflow/src/workflow.rs (1)

119-125: Consider using mutable reference for consistency.

While the current implementation works, most builder methods in this file now use &mut self. Consider changing add_job to use &mut self for consistency with the new API pattern.

-    pub fn add_job<T: ToString, J: Into<Job>>(mut self, id: T, job: J) -> Self {
+    pub fn add_job<T: ToString, J: Into<Job>>(&mut self, id: T, job: J) -> &mut Self {
         let key = id.to_string();
         let jobs = self.jobs.take().unwrap_or_default().add(key, job.into());
 
         self.jobs = Some(jobs);
         self
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f22b754 and e9f4d05.

📒 Files selected for processing (4)
  • crates/gh-workflow-tailcall/src/standard.rs (5 hunks)
  • crates/gh-workflow/src/cargo.rs (0 hunks)
  • crates/gh-workflow/src/generate.rs (1 hunks)
  • crates/gh-workflow/src/workflow.rs (10 hunks)
💤 Files with no reviewable changes (1)
  • crates/gh-workflow/src/cargo.rs
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.
📚 Learning: 2024-12-10T06:33:21.046Z
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.

Applied to files:

  • crates/gh-workflow-tailcall/src/standard.rs
  • crates/gh-workflow/src/workflow.rs
🧬 Code graph analysis (1)
crates/gh-workflow-tailcall/src/standard.rs (2)
crates/gh-workflow/src/workflow.rs (8)
  • new (109-111)
  • new (229-235)
  • new (345-347)
  • new (628-638)
  • new (659-662)
  • new (800-802)
  • uses (457-471)
  • uses (497-506)
crates/gh-workflow/src/cargo.rs (1)
  • new (28-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test
🔇 Additional comments (15)
crates/gh-workflow-tailcall/src/standard.rs (6)

29-29: Great addition of setters attributes!

The addition of #[setters(strip_option, into)] improves the builder API ergonomics by allowing users to pass values directly without wrapping them in Option.


92-95: Well-designed public API for adding setup steps.

The method signature is flexible with Into<Step<Run>> and follows builder pattern conventions by returning Self.


119-131: Improved incremental workflow building approach.

The refactor to build the workflow incrementally using a mutable variable is cleaner and more maintainable than chaining multiple conditional calls.


141-142: Good fix for release job dependencies.

Using explicit job IDs ("build", "lint") instead of reconstructed Job objects correctly matches the actual job IDs used in to_ci_workflow().


162-202: Clean refactor of lint job with proper conditional logic.

The lint job is well-structured with clear separation between fmt and clippy steps, and proper conditional handling for autofix mode. The use of mutable builders for each step improves readability.


207-237: Well-structured test job with proper nextest and caching support.

The test job refactor properly handles the conditional installation of nextest and adds appropriate caching with rust-cache. The conditional benchmark step is cleanly integrated.

crates/gh-workflow/src/workflow.rs (7)

20-23: Clean API change for Jobs collection.

The change from insert to add with owned self and return of Self aligns well with the builder pattern used throughout the codebase.


138-155: Consistent API improvement with mutable references.

The changes to add_event and add_env properly implement the mutable reference pattern, making the API more consistent and efficient.


188-188: Good change to make Job.needs publicly accessible.

Making needs public aligns with the removal of tmp_needs and simplifies the API by allowing direct access to job dependencies.


256-263: Well-implemented add_needs method.

The method properly handles both initial and subsequent additions to the needs vector, maintaining clean builder semantics.


477-483: Proper use of take() for interior mutation.

The consistent use of take() on Option fields for in-place mutation is a good pattern that avoids unnecessary clones while maintaining ownership semantics.

Also applies to: 514-524


711-711: Addition of Copy trait improves ergonomics.

Adding Copy to the Level enum is appropriate since it's a simple enum without heap-allocated data, making it more convenient to use.


795-795: Good addition of serde(transparent) for cleaner serialization.

The #[serde(transparent)] attribute ensures Expression serializes directly as a string without wrapper structure, improving YAML output.

crates/gh-workflow/src/generate.rs (2)

13-13: Consistent setter attributes across the codebase.

The addition of strip_option to the setters aligns with the changes in other modules, providing a consistent API experience.


10-10: Simplified constructor after removing dependency organization.

The removal of organize_job_dependency call simplifies the constructor and respects the workflow structure as provided by the caller, which is a cleaner approach.

Also applies to: 21-22

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🧹 Nitpick comments (13)
crates/gh-workflow/src/container.rs (2)

64-89: Validate volume parsing and align serialization with GitHub format

  • String format for volumes in workflows is typically "/host:/container"; modeling as a map may not match expected YAML. Confirm how this struct is serialized; you might need custom (de)serialize to emit a single string.
  • Parsing with split(':') is fragile (Windows paths, extra segments like :ro). Prefer splitn(2, ':') and basic non-empty checks.
-    pub fn new(volume_str: &str) -> Option<Self> {
-        let parts: Vec<&str> = volume_str.split(':').collect();
-        if parts.len() == 2 {
-            Some(Volume {
-                source: parts[0].to_string(),
-                destination: parts[1].to_string(),
-            })
-        } else {
-            None
-        }
-    }
+    pub fn new(volume_str: &str) -> Option<Self> {
+        let mut it = volume_str.splitn(2, ':');
+        let source = it.next().unwrap_or_default().trim();
+        let destination = it.next().unwrap_or_default().trim();
+        if source.is_empty() || destination.is_empty() { return None; }
+        Some(Volume { source: source.to_string(), destination: destination.to_string() })
+    }

If you confirm GitHub expects strings, consider:

// Emit "source:destination" as a single string in YAML.
impl serde::Serialize for Volume { /* format as "{source}:{destination}" */ }
impl<'de> serde::Deserialize<'de> for Volume { /* parse from string */ }

9-15: Provide a constructor to ensure required image is always set

Container::default() yields image = "". Offer Container::new(image) to prevent invalid instances while keeping builder ergonomics.

impl Container {
    pub fn new(image: impl Into<String>) -> Self {
        Self { image: image.into(), ..Default::default() }
    }
}
crates/gh-workflow/src/strategy.rs (1)

13-24: Optional: document matrix shape and add light helpers

Add a short doc/example for expected matrix JSON and consider a with_matrix(Value) helper to avoid accidental wrong shapes.

crates/gh-workflow/src/artifacts.rs (1)

21-35: Differentiate upload vs download fields for type-safety

retention_days applies to uploads, not downloads. Consider separate types (UploadArtifact with retention_days, DownloadArtifact without) to prevent invalid combinations and improve codegen.

If you keep a single Artifact, ensure step generation omits retention-days for downloads.

crates/gh-workflow/src/env.rs (2)

22-31: Confirm GITHUB_TOKEN expression is emitted unquoted in YAML.

Storing ${{ secrets.GITHUB_TOKEN }} as a JSON string can get quoted by YAML emitters, breaking the expression. If you have an Expression newtype in this crate, prefer using it here; otherwise, please verify the generated YAML keeps the token unquoted.


38-43: Minor generics polish.

Rename T1/T2 to K/V for readability; no behavioral change.

-    pub fn add<T1: ToString, T2: Into<Value>>(mut self, key: T1, value: T2) -> Self {
+    pub fn add<K: ToString, V: Into<Value>>(mut self, key: K, value: V) -> Self {
crates/gh-workflow/src/environment.rs (1)

12-18: Consider supporting expressions for name/url.

GitHub allows expressions in environment name/url. If your serializer quotes strings, expressions may break. Optionally switch these fields to an Expression or serde_json::Value to preserve raw expressions.

crates/gh-workflow/src/lib.rs (1)

1-1: Drop redundant extern crate

extern crate proc_macro; is unnecessary for non-proc-macro crates on Rust 2018+ editions. Safe to remove.

-extern crate proc_macro;
crates/gh-workflow/src/job.rs (2)

95-101: Env merge is fine; minor ergonomics

Using Option::take() is good. If Env’s inner map remains public, keep as-is; otherwise consider an Env::merge to avoid touching env.0.


52-56: Add job-level with for reusable workflows

Jobs that uses: a reusable workflow often require with: inputs. Consider adding with: Option<IndexMap<String, Value>> to Job for parity with YAML.

     #[serde(skip_serializing_if = "Option::is_none")]
     pub uses: Option<String>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub with: Option<IndexMap<String, Value>>,

Docs reference jobs.<job_id>.uses/with. (docs.github.com)

crates/gh-workflow/src/workflow.rs (3)

24-27: Generalize Jobs::add signature to improve ergonomics and consistency

Align with Env::add/Step::add and avoid forcing callers to allocate a String or construct Job eagerly.

Apply:

-    pub fn add(mut self, key: String, value: Job) -> Self {
-        self.0.insert(key, value);
+    pub fn add<K: ToString, J: Into<Job>>(mut self, key: K, value: J) -> Self {
+        self.0.insert(key.to_string(), value.into());
         self
     }

123-129: Clarify/guard duplicate job IDs

Repeated add_job with the same id silently overwrites. If overwrite is intended, document it; otherwise consider a checked variant (e.g., add_job_unique) that returns Result or asserts in debug.


153-159: Avoid reaching into Env internals; add an extend/merge helper on Env

Hide tuple-field access and make precedence explicit. Also clarifies that new_env values overwrite existing keys.

Apply here:

-        let mut env = self.env.take().unwrap_or_default();
-
-        env.0.extend(new_env.into().0);
-        self.env = Some(env);
+        let env = self.env
+            .take()
+            .unwrap_or_default()
+            .extend(new_env.into());
+        self.env = Some(env);

And add in crates/gh-workflow/src/env.rs:

impl Env {
    pub fn extend(mut self, other: Env) -> Self {
        self.0.extend(other.0);
        self
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between e9f4d05 and 822c3fa.

📒 Files selected for processing (14)
  • crates/gh-workflow/src/artifacts.rs (1 hunks)
  • crates/gh-workflow/src/concurrency.rs (1 hunks)
  • crates/gh-workflow/src/container.rs (1 hunks)
  • crates/gh-workflow/src/defaults.rs (1 hunks)
  • crates/gh-workflow/src/env.rs (1 hunks)
  • crates/gh-workflow/src/environment.rs (1 hunks)
  • crates/gh-workflow/src/expression.rs (1 hunks)
  • crates/gh-workflow/src/job.rs (1 hunks)
  • crates/gh-workflow/src/lib.rs (2 hunks)
  • crates/gh-workflow/src/permissions.rs (1 hunks)
  • crates/gh-workflow/src/secret.rs (1 hunks)
  • crates/gh-workflow/src/step.rs (1 hunks)
  • crates/gh-workflow/src/strategy.rs (1 hunks)
  • crates/gh-workflow/src/workflow.rs (3 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.
📚 Learning: 2024-12-10T06:33:21.046Z
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.

Applied to files:

  • crates/gh-workflow/src/workflow.rs
🧬 Code graph analysis (7)
crates/gh-workflow/src/concurrency.rs (1)
crates/gh-workflow/src/expression.rs (1)
  • new (13-15)
crates/gh-workflow/src/container.rs (1)
crates/gh-workflow/src/env.rs (1)
  • new (34-36)
crates/gh-workflow/src/expression.rs (1)
crates/gh-workflow/src/concurrency.rs (1)
  • new (24-27)
crates/gh-workflow/src/step.rs (3)
crates/gh-workflow/src/env.rs (4)
  • from (17-19)
  • from (48-52)
  • add (39-42)
  • new (34-36)
crates/gh-workflow/src/job.rs (3)
  • from (25-27)
  • add_env (96-102)
  • new (77-83)
crates/gh-workflow/src/expression.rs (1)
  • new (13-15)
crates/gh-workflow/src/env.rs (1)
crates/gh-workflow/src/step.rs (5)
  • from (24-26)
  • from (31-33)
  • from (74-76)
  • from (241-245)
  • add (88-91)
crates/gh-workflow/src/job.rs (2)
crates/gh-workflow/src/env.rs (3)
  • from (17-19)
  • from (48-52)
  • new (34-36)
crates/gh-workflow/src/step.rs (8)
  • from (24-26)
  • from (31-33)
  • from (74-76)
  • from (241-245)
  • to_value (47-47)
  • to_value (55-57)
  • to_value (62-64)
  • add_env (188-194)
crates/gh-workflow/src/workflow.rs (2)
crates/gh-workflow/src/env.rs (1)
  • add (39-42)
crates/gh-workflow/src/step.rs (1)
  • add (88-91)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test
🔇 Additional comments (15)
crates/gh-workflow/src/secret.rs (1)

8-11: Confirm setter receiver style in generated setters
The derive_setters macro attributes are present, but we couldn’t verify from source if setters consume self. Please inspect the expanded macro output (e.g., via cargo expand) for Secret (and other types) to ensure setters use fn field(mut self) -> Self rather than &mut self.

crates/gh-workflow/src/artifacts.rs (1)

11-19: LGTM on overall structure

Fields, serde settings, and setters align with the crate’s builder pattern and kebab-case convention.

crates/gh-workflow/src/permissions.rs (2)

52-60: LGTM on the model and serde shape.

Level defaults to none and fields are skipped when None; kebab-case matches Actions schema (e.g., pull-requests, id-token).


7-10: No action needed: derive_setters already generates consuming setters
The #[setters(strip_option, into)] attribute without borrow_self produces fn field(self, …) -> Self methods by default, matching the consuming‐builder pattern. No manual with_* methods are required.

Likely an incorrect or invalid review comment.

crates/gh-workflow/src/environment.rs (1)

8-11: derive_setters emits consuming setters by default. No manual with_* methods required. (lib.rs)

crates/gh-workflow/src/lib.rs (1)

26-41: Re-exports and module split look good

The crate-level re-exports nicely consolidate the public API and align with the builder ergonomics.

crates/gh-workflow/src/concurrency.rs (1)

24-27: Drop visibility-based concern Expression’s inner String is already public (pub struct Expression(pub String)), so using expr.0 is valid across modules.

Likely an incorrect or invalid review comment.

crates/gh-workflow/src/job.rs (1)

75-83: Sane defaults; builder style LGTM

Job::new defaulting to ubuntu-latest and chaining via Self is consistent with the PR’s ergonomics.

crates/gh-workflow/src/defaults.rs (2)

22-25: Concurrency in defaults: OK (once Concurrency is valid)

This is fine provided Concurrency only serializes supported keys (drop limit per earlier comment).


50-57: Retry types are internal? Ensure not serialized to invalid YAML

If RetryStrategy maps to native Actions keys, great. If not, keep it internal (e.g., consumed by generate.rs) and avoid serializing raw retry: at job/step level (not an official key).

Docs list allowed keys and do not include retry. (docs.github.com)

crates/gh-workflow/src/step.rs (3)

219-222: Checkout default version v5: verify runner compatibility

actions/checkout v5 requires runner ≥ v2.327.1. Fine for GitHub-hosted; self-hosted users must update.

Reference: actions/checkout README shows v5 and runner requirement. (github.com)


224-235: with merging is neat

Using Merge for Input and stripping empty maps to None keeps YAML clean. Nice.


99-109: Delegated setters: good for chaining

The generate_delegates to Step/Step provides fluent APIs without duplication.

crates/gh-workflow/src/workflow.rs (2)

9-19: Imports modularization looks good

Imports align with the new module split and keep the surface tidy.


142-150: Event merge logic: LGTM

Using take()+merge keeps ownership clean and composes multiple add_event calls predictably.

Comment on lines +14 to +17
/// Whether to cancel in-progress jobs.
#[serde(skip_serializing_if = "Option::is_none")]
pub cancel_in_progress: Option<bool>,

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Allow expressions for cancel-in-progress

Docs allow cancel-in-progress to be a boolean OR an expression. Consider modeling it with an untagged enum so YAML stays valid for both forms.

-    /// Whether to cancel in-progress jobs.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub cancel_in_progress: Option<bool>,
+    /// Whether to cancel in-progress jobs; supports bool or expression.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub cancel_in_progress: Option<CancelInProgress>,

Add alongside the struct:

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum CancelInProgress {
    Bool(bool),
    Expr(Expression),
}

Refs. (docs.github.com)

🤖 Prompt for AI Agents
In crates/gh-workflow/src/concurrency.rs around lines 14 to 17, the
cancel_in_progress field is typed as Option<bool> but GitHub allows a boolean or
an expression; change this by adding an untagged enum CancelInProgress with
variants Bool(bool) and Expr(Expression) (derive Debug, Serialize, Deserialize,
Clone, PartialEq, Eq) and then change the struct field to pub
cancel_in_progress: Option<CancelInProgress> (keep #[serde(skip_serializing_if =
"Option::is_none")] on the field); ensure Expression is in scope/imported and
adjust any usages/serialization expectations accordingly.

Comment on lines +18 to +20
/// The limit on concurrent jobs.
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove unsupported field: concurrency.limit

GitHub Actions concurrency supports only group and cancel-in-progress; limit is not a valid key and would generate invalid YAML if emitted. Drop the field.

-    /// The limit on concurrent jobs.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub limit: Option<u32>,

References: official docs show only group and cancel-in-progress. (docs.github.com)

📝 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
/// The limit on concurrent jobs.
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<u32>,
🤖 Prompt for AI Agents
In crates/gh-workflow/src/concurrency.rs around lines 18-20, remove the
unsupported pub limit: Option<u32> field and any serde attributes referencing
it; update the struct to only include fields valid for GitHub Actions
concurrency (e.g., group and cancel_in_progress) and remove usages/tests that
serialize or construct the limit field so the generated YAML no longer emits an
invalid concurrency.limit key.

Comment on lines +53 to +62
/// Represents a network port.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Port {
/// A port specified by its number.
Number(u16),

/// A port specified by its name.
Name(String),
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Ports serialize incorrectly; use untagged enum

Without #[serde(untagged)], YAML becomes {number: 80}/{name: "http"} instead of bare 80/"http". This breaks GitHub schema expectations.

 /// Represents a network port.
 #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
-#[serde(rename_all = "kebab-case")]
+#[serde(untagged)]
 pub enum Port {
   /// A port specified by its number.
   Number(u16),
 
   /// A port specified by its name.
   Name(String),
 }
📝 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
/// Represents a network port.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
pub enum Port {
/// A port specified by its number.
Number(u16),
/// A port specified by its name.
Name(String),
}
/// Represents a network port.
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum Port {
/// A port specified by its number.
Number(u16),
/// A port specified by its name.
Name(String),
}
🤖 Prompt for AI Agents
In crates/gh-workflow/src/container.rs around lines 53 to 62, the Port enum
currently serializes as tagged maps (e.g. {number: 80}) but GitHub expects bare
values (80 or "http"); add the serde untagged attribute to the enum by
annotating it with #[serde(untagged)] so the Number(u16) and Name(String)
variants serialize as plain scalar values, leaving the existing rename_all
attribute alone.

Comment on lines +11 to +13
#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
#[serde(transparent)]
pub struct Env(pub IndexMap<String, Value>);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Deriving Eq here won’t compile (serde_json::Value does not implement Eq).

Env wraps IndexMap<String, Value>; serde_json::Value only implements PartialEq. Drop Eq from the derive list.

-#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone, PartialEq)]
🤖 Prompt for AI Agents
In crates/gh-workflow/src/env.rs around lines 11 to 13, the struct derives Eq
which fails to compile because serde_json::Value does not implement Eq; remove
Eq from the derive list (leave Default, Debug, Serialize, Deserialize, Clone,
PartialEq) so Env derives only traits supported by its inner types and recompile
to confirm the error is resolved.

Comment on lines +45 to +53
/// Represents environment variables as key-value pairs.
impl<S1: Display, S2: Display> From<(S1, S2)> for Env {
/// Converts a tuple into an `Env`.
fn from(value: (S1, S2)) -> Self {
let mut index_map: IndexMap<String, Value> = IndexMap::new();
index_map.insert(value.0.to_string(), Value::String(value.1.to_string()));
Env(index_map)
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Broaden tuple conversion to accept non-string values.

Current impl forces the value to String. Accept any Into so booleans/numbers/expressions don’t get stringified.

-impl<S1: Display, S2: Display> From<(S1, S2)> for Env {
+impl<K: ToString, V: Into<Value>> From<(K, V)> for Env {
     /// Converts a tuple into an `Env`.
-    fn from(value: (S1, S2)) -> Self {
+    fn from(value: (K, V)) -> Self {
         let mut index_map: IndexMap<String, Value> = IndexMap::new();
-        index_map.insert(value.0.to_string(), Value::String(value.1.to_string()));
+        index_map.insert(value.0.to_string(), value.1.into());
         Env(index_map)
     }
 }
📝 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
/// Represents environment variables as key-value pairs.
impl<S1: Display, S2: Display> From<(S1, S2)> for Env {
/// Converts a tuple into an `Env`.
fn from(value: (S1, S2)) -> Self {
let mut index_map: IndexMap<String, Value> = IndexMap::new();
index_map.insert(value.0.to_string(), Value::String(value.1.to_string()));
Env(index_map)
}
}
/// Represents environment variables as key-value pairs.
impl<K: ToString, V: Into<Value>> From<(K, V)> for Env {
/// Converts a tuple into an `Env`.
fn from(value: (K, V)) -> Self {
let mut index_map: IndexMap<String, Value> = IndexMap::new();
index_map.insert(value.0.to_string(), value.1.into());
Env(index_map)
}
}
🤖 Prompt for AI Agents
In crates/gh-workflow/src/env.rs around lines 45 to 53, the From<(S1,S2)> impl
currently forces the value to a String which stringifies booleans/numbers;
change the impl to accept a key type convertible to String and a value type
convertible Into<Value> (e.g. impl<K: Into<String>, V: Into<Value>> From<(K, V)>
for Env), call key.into() for the map key and value.into() for the Value, and
ensure the Value type is in scope (add use if necessary) so non-string values
are preserved instead of being stringified.

Comment on lines +11 to +16
impl Expression {
/// Creates a new `Expression` from a string.
pub fn new<T: ToString>(expr: T) -> Self {
Self(expr.to_string())
}
}
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Into<Expression> usage will fail without From impls

Call sites like Concurrency::new("group") require From<&str>/From<String> for Expression. Add them to make impl Into<Expression> ergonomic.

 impl Expression {
   /// Creates a new `Expression` from a string.
   pub fn new<T: ToString>(expr: T) -> Self {
     Self(expr.to_string())
   }
 }
+
+impl From<&str> for Expression {
+    fn from(s: &str) -> Self { Self(s.to_string()) }
+}
+
+impl From<String> for Expression {
+    fn from(s: String) -> Self { Self(s) }
+}

🏁 Script executed:

#!/bin/bash
# Verify call sites expecting Into<Expression> compile ergonomically.
rg -n 'Into<Expression>' -C3
rg -nP 'new\([^)]*"(?:[^"]*)"\)' crates/gh-workflow/src | sed -n '1,200p'

Length of output: 642


Implement From<&str> and From for Expression
Add these impls in crates/gh-workflow/src/expression.rs (below the existing impl Expression) so that &str and String automatically convert into Expression, enabling ergonomic calls like Concurrency::new("group"):

 impl Expression {
     /// Creates a new `Expression` from a string.
     pub fn new<T: ToString>(expr: T) -> Self {
         Self(expr.to_string())
     }
 }

+impl From<&str> for Expression {
+    fn from(s: &str) -> Self { Self(s.to_string()) }
+}
+
+impl From<String> for Expression {
+    fn from(s: String) -> Self { Self(s) }
+}
📝 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
impl Expression {
/// Creates a new `Expression` from a string.
pub fn new<T: ToString>(expr: T) -> Self {
Self(expr.to_string())
}
}
impl Expression {
/// Creates a new `Expression` from a string.
pub fn new<T: ToString>(expr: T) -> Self {
Self(expr.to_string())
}
}
impl From<&str> for Expression {
fn from(s: &str) -> Self {
Self(s.to_string())
}
}
impl From<String> for Expression {
fn from(s: String) -> Self {
Self(s)
}
}
🤖 Prompt for AI Agents
In crates/gh-workflow/src/expression.rs around lines 11 to 16, add
implementations of the From trait for &str and String for Expression (placed
below the existing impl Expression block); implement From<&str> by converting
the &str to String and calling Expression::new (or Expression(String)), and
implement From<String> by wrapping the String into an Expression directly, so
users can pass &str and String where an Expression is expected.

Comment on lines +12 to +17
/// Indicates if the secret is required.
pub required: bool,

/// A description of the secret.
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Make required backward-compatible and omit falsey defaults in YAML

Without a default, deserialization fails when required is omitted; and always emitting required: false is noisy. Add a default and skip serializing false.

-    /// Indicates if the secret is required.
-    pub required: bool,
+    /// Indicates if the secret is required.
+    #[serde(default, skip_serializing_if = "is_false")]
+    pub required: bool,

Add this helper in the module:

fn is_false(b: &bool) -> bool { !*b }
🤖 Prompt for AI Agents
In crates/gh-workflow/src/secret.rs around lines 12 to 17, the pub required:
bool field needs a serde default and should skip serializing when false to be
backward-compatible and avoid noisy YAML; add a module helper fn is_false(b:
&bool) -> bool { !*b } and annotate the field with #[serde(default,
skip_serializing_if = "is_false")] so missing values deserialize as false and
false values are omitted from serialized YAML.

Comment on lines +152 to +159
/// The retry strategy for the step.
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryStrategy>,

/// Artifacts produced by the step.
#[serde(skip_serializing_if = "Option::is_none")]
pub artifacts: Option<Artifacts>,
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Do not emit non-standard retry in steps

retry is not an official step key. If you intend to expand it in generate.rs to a pattern (e.g., wrapper action), mark it #[serde(skip)] to avoid invalid YAML.

-    /// The retry strategy for the step.
-    #[serde(skip_serializing_if = "Option::is_none")]
-    pub retry: Option<RetryStrategy>,
+    /// The retry strategy for the step (internal; expanded by generator).
+    #[serde(skip)]
+    pub retry: Option<RetryStrategy>,

Official step keys: id, name, if, uses, with, run, shell, env, timeout-minutes, continue-on-error, working-directory. (docs.github.com)

📝 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
/// The retry strategy for the step.
#[serde(skip_serializing_if = "Option::is_none")]
pub retry: Option<RetryStrategy>,
/// Artifacts produced by the step.
#[serde(skip_serializing_if = "Option::is_none")]
pub artifacts: Option<Artifacts>,
}
/// The retry strategy for the step (internal; expanded by generator).
#[serde(skip)]
pub retry: Option<RetryStrategy>,
/// Artifacts produced by the step.
#[serde(skip_serializing_if = "Option::is_none")]
pub artifacts: Option<Artifacts>,
}
🤖 Prompt for AI Agents
crates/gh-workflow/src/step.rs around lines 152 to 159: the Step struct
currently serializes a non-standard `retry` field which will emit invalid YAML
for GitHub Actions steps; change its serde attribute to skip serialization
(e.g., #[serde(skip)]) so the field is available in Rust for internal use or
transformation but will not be emitted into the generated workflow YAML, or
remove the field entirely if unused.

Comment on lines +8 to +12
/// Represents the strategy for running jobs.
#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[setters(strip_option, into)]
pub struct Strategy {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Eq derive conflicts with serde_json::Value

serde_json::Value does not implement Eq, so #[derive(Eq)] on Strategy will not compile.

-#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
+#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq)]
 pub struct Strategy {
📝 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
/// Represents the strategy for running jobs.
#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
#[serde(rename_all = "kebab-case")]
#[setters(strip_option, into)]
pub struct Strategy {
/// Represents the strategy for running jobs.
#[derive(Debug, Setters, Serialize, Deserialize, Clone, Default, PartialEq)]
#[serde(rename_all = "kebab-case")]
#[setters(strip_option, into)]
pub struct Strategy {
🤖 Prompt for AI Agents
In crates/gh-workflow/src/strategy.rs around lines 8 to 12, the #[derive(...,
Eq)] on Strategy fails because serde_json::Value does not implement Eq; remove
Eq from the derive list (leave PartialEq) so the struct derives PartialEq but
not Eq, or alternatively replace Eq with a manual implementation only if all
fields support Eq; update the attribute to #[derive(Debug, Setters, Serialize,
Deserialize, Clone, Default, PartialEq)].

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
crates/gh-workflow/src/toolchain.rs (2)

223-229: ABI is concatenated without a hyphen (generates invalid targets like linuxmusl)

Format becomes x86_64-unknown-linuxmusl instead of x86_64-unknown-linux-musl.

Apply:

-            let target = format!(
-                "{}-{}-{}{}",
-                target.arch,
-                target.vendor,
-                target.system,
-                target.abi.map(|v| v.to_string()).unwrap_or_default(),
-            );
+            let abi = target.abi.map(|v| format!("-{}", v)).unwrap_or_default();
+            let target = format!(
+                "{}-{}-{}{}",
+                target.arch,
+                target.vendor,
+                target.system,
+                abi,
+            );

255-259: Leading newline in cache-workspaces

Fold introduces a leading newline. Use join like cache_directories.

-            let cache_workspaces = value
-                .cache_workspaces
-                .iter()
-                .fold("".to_string(), |acc, a| format!("{acc}\n{a}"));
+            let cache_workspaces = value.cache_workspaces.join("\n");
♻️ Duplicate comments (1)
crates/gh-workflow/src/step.rs (1)

100-159: Do not serialize non-standard step fields (retry, artifacts)

These are not valid GitHub Actions step keys and will emit invalid YAML unless expanded elsewhere. Mark them to skip serialization.

     /// The retry strategy for the step.
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(skip)]
     pub retry: Option<RetryStrategy>,

     /// Artifacts produced by the step.
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(skip)]
     pub artifacts: Option<Artifacts>,

If a generator expands them, add docs/comments noting they’re internal-only.

🧹 Nitpick comments (1)
crates/gh-workflow/src/toolchain.rs (1)

204-214: Prefer join over reduce+format for comma-separated lists

Simpler and avoids intermediate allocations.

-        let toolchain = value
-            .toolchain
-            .iter()
-            .map(|t| match t {
-                Version::Stable => "stable".to_string(),
-                Version::Nightly => "nightly".to_string(),
-                Version::Custom((major, minor, patch)) => {
-                    format!("{major}.{minor}.{patch}")
-                }
-            })
-            .reduce(|acc, a| format!("{acc}, {a}"));
+        let toolchain = {
+            let v: Vec<String> = value.toolchain.iter().map(|t| match t {
+                Version::Stable => "stable".to_string(),
+                Version::Nightly => "nightly".to_string(),
+                Version::Custom((major, minor, patch)) => format!("{major}.{minor}.{patch}"),
+            }).collect();
+            (!v.is_empty()).then(|| v.join(", "))
+        };
-            let components = value
-                .components
-                .iter()
-                .map(|c| c.to_string())
-                .reduce(|acc, a| format!("{acc}, {a}"))
-                .unwrap_or_default();
+            let components = value.components.iter().map(|c| c.to_string()).collect::<Vec<_>>().join(", ");

Also applies to: 235-241

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 822c3fa and 7f612be.

📒 Files selected for processing (6)
  • .github/workflows/autofix.yml (1 hunks)
  • crates/gh-workflow-tailcall/src/standard.rs (5 hunks)
  • crates/gh-workflow/src/cargo.rs (2 hunks)
  • crates/gh-workflow/src/release_plz.rs (1 hunks)
  • crates/gh-workflow/src/step.rs (1 hunks)
  • crates/gh-workflow/src/toolchain.rs (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • .github/workflows/autofix.yml
🚧 Files skipped from review as they are similar to previous changes (2)
  • crates/gh-workflow/src/cargo.rs
  • crates/gh-workflow-tailcall/src/standard.rs
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: tusharmath
PR: tailcallhq/gh-workflow#97
File: crates/gh-workflow-tailcall/src/workflow.rs:121-121
Timestamp: 2024-12-10T06:33:21.046Z
Learning: In `crates/gh-workflow-tailcall/src/workflow.rs`, prefer converting standalone functions into methods of the `Workflow` struct when appropriate.
🧬 Code graph analysis (3)
crates/gh-workflow/src/toolchain.rs (1)
crates/gh-workflow/src/step.rs (1)
  • new (198-203)
crates/gh-workflow/src/release_plz.rs (1)
crates/gh-workflow/src/step.rs (1)
  • new (198-203)
crates/gh-workflow/src/step.rs (5)
crates/gh-workflow/src/cargo.rs (2)
  • from (64-93)
  • new (28-36)
crates/gh-workflow/src/release_plz.rs (1)
  • from (75-107)
crates/gh-workflow/src/toolchain.rs (2)
  • from (197-287)
  • new (28-30)
crates/gh-workflow/src/env.rs (4)
  • from (17-19)
  • from (48-52)
  • add (39-42)
  • new (34-36)
crates/gh-workflow/src/job.rs (3)
  • from (25-27)
  • add_env (96-102)
  • new (77-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build and Test
🔇 Additional comments (8)
crates/gh-workflow/src/toolchain.rs (1)

198-202: Constructor switch to Step::new(...).uses(...) looks good

Keeps behavior while aligning with the new builder surface.

crates/gh-workflow/src/release_plz.rs (2)

76-76: Builder-style step construction LGTM

Matches the new Step API and keeps semantics.


87-88: No change required for input key casing
The release-plz/[email protected] officially defines manifest_path (with an underscore) as the input key, so the existing code is correct.

crates/gh-workflow/src/step.rs (5)

12-20: serde-transparent Step wrapper is correct

Layout and markers are serialization-safe.


185-195: Env merge via Option::take is clean

Nice usage to avoid clones.


197-219: Typed Step constructors read well

The Step::new -> uses/run flow enables fluent chaining and enforces types.


44-65: StepType is used in crates/gh-workflow/src/job.rs (in the add_step signature), so it isn’t dead code.

Likely an incorrect or invalid review comment.


221-227: actions/checkout@v5 validated v5.0.0 (released Aug 11, 2025) is the latest stable major version; update GitHub Actions runners to v2.327.1+ with Node 24+ to ensure compatibility. [1][3]

@@ -73,7 +73,7 @@ impl std::fmt::Display for Backend {

impl From<Release> for Step<Use> {
fn from(value: Release) -> Self {
let mut step = Step::uses("release-plz", "action", "v0.5").name("Release Plz");
let mut step = Step::new("Release Plz").uses("release-plz", "action", "v0.5");
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Pin version check

Ensure v0.5 is still current or intentionally pinned; consider semver range or digest if supply-chain constraints apply.


🌐 Web query:

What is the latest recommended tag for release-plz/action as of September 2025, and are there security/bugfix notes since v0.5?

💡 Result:

  • Recommended tag (Sept 2025): use the v0.5 series — e.g. the repository’s latest v0.5 release is v0.5.113 (Sep 1, 2025). [1]

  • Security / bugfix notes since v0.5:

    • Documentation recommends pinning the action (either a specific v0.5.x tag or a commit SHA) to mitigate risk of malicious updates; it explains the risk and shows how to pin to a commit SHA. [2]
    • The v0.5.* release stream contains many small bugfix/maintenance updates (each v0.5.N updates the bundled release-plz version and dependency bumps); see the releases/changelog for per-release details. [1][3]

Sources:

  1. release-plz/action releases (latest v0.5.113).
  2. release-plz GitHub Action security docs (pinning / risks).
  3. release-plz troubleshooting / changelog notes.

Pin to specific patch or digest
In crates/gh-workflow/src/release_plz.rs:76, change

-let mut step = Step::new("Release Plz").uses("release-plz", "action", "v0.5");
+let mut step = Step::new("Release Plz").uses("release-plz", "action", "v0.5.113");

—or alternatively pin to a commit SHA in the v0.5 series to prevent unintended updates and mitigate supply-chain risks.

📝 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
let mut step = Step::new("Release Plz").uses("release-plz", "action", "v0.5");
let mut step = Step::new("Release Plz").uses("release-plz", "action", "v0.5.113");
🤖 Prompt for AI Agents
In crates/gh-workflow/src/release_plz.rs around line 76, the action is currently
referenced as the floating tag "v0.5"; replace that with a pinned patch tag or
commit SHA from the v0.5 series (e.g. "v0.5.3" or the full commit SHA) or a
verified digest to avoid implicit updates — update the third argument of
.uses(...) accordingly and commit the exact tag or SHA.

@tusharmath tusharmath merged commit 13f2c10 into main Sep 2, 2025
5 of 6 checks passed
@github-actions github-actions bot mentioned this pull request Sep 2, 2025
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