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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 28 additions & 8 deletions .github/workflows/run-codegen-pull-request-task.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -23,8 +23,27 @@ on:

jobs:

# Capture ONE UTC timestamp shared by both matrix legs below. `--runtime`
# needs the same value on the `main` and `develop` legs so CodeGen.cs comes
# out byte-identical and `develop -> main` release merges don't conflict on
# it. `github.run_started_at` was used for this but resolves to an empty
# string in this reusable-workflow context, so each leg fell back to its own
# `DateTime.UtcNow` and the outputs diverged — capture the value here instead.
get-runtime:
name: Capture shared runtime job
runs-on: ubuntu-latest
outputs:
runtime: ${{ steps.runtime.outputs.runtime }}
steps:
- name: Capture shared runtime step
id: runtime
run: |
set -euo pipefail
echo "runtime=$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$GITHUB_OUTPUT"

codegen:
name: Run ${{ matrix.target.ref }} codegen and pull request job
needs: [get-runtime]
runs-on: ubuntu-latest
permissions:
contents: write
Expand DownExpand Up@@ -65,19 +84,20 @@ jobs:
token: ${{ steps.app-token.outputs.token }}

- name: Run codegen step
# `--runtime` is template-internal hygiene: passing the workflow's
# `run_started_at` to both matrix legs (main and develop) makes them
# produce byte-identical CodeGen.cs, so develop->main release merges
# don't conflict on this demo file every release. Derived projects'
# real codegen should not copy this pattern — if your generator's
# per-run state is intentional, design it not to land on multiple
# release branches simultaneously, or absorb the merge cost.
# `--runtime` is template-internal hygiene: passing the SAME timestamp
# (captured once in the `get-runtime` job above) to both matrix legs
# (main and develop) makes them produce byte-identical CodeGen.cs, so
# develop->main release merges don't conflict on this demo file every
# release. Derived projects' real codegen should not copy this pattern —
# if your generator's per-run state is intentional, design it not to
# land on multiple release branches simultaneously, or absorb the merge
# cost.
run: |
set -euo pipefail
dotnet run --project ./CodeGen/CodeGen.csproj -- \
--codepath ./CodeGen \
--apikey "${{ secrets.NINJA_API_KEY }}" \
--runtime "${{ github.run_started_at }}"
--runtime "${{ needs.get-runtime.outputs.runtime }}"

- name: Format code step
run: |
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,7 +27,7 @@ Treat this file as authoritative for everything else; don't restate its rules el
- **Bots (Dependabot and codegen) target both `main` and `develop` in parallel.** [`.github/dependabot.yml`](./.github/dependabot.yml) duplicates every ecosystem entry (one per branch) and [`.github/workflows/run-codegen-pull-request-task.yml`](./.github/workflows/run-codegen-pull-request-task.yml) runs as a matrix over both branches with branch names `codegen-main` and `codegen-develop`. Each branch absorbs its own bot PRs independently, so neither falls behind, and the forward-only rule still holds (nothing is back-merged from main to develop — both branches receive their updates directly). The merge-bot ([`.github/workflows/merge-bot-pull-request.yml`](./.github/workflows/merge-bot-pull-request.yml)) dispatches `--squash` or `--merge` from each PR's base ref via a `case` statement so the form matches the ruleset on either base. Dependabot **security** PRs (CVE-driven) always open against the repo default branch (`main`) regardless of `target-branch` — the same `case` statement covers them.
- **Maintainer-pushed commits on a bot PR auto-disable auto-merge.** The merge-bot's `merge-dependabot` and `merge-codegen` jobs only fire on `opened` / `reopened` events (auto-merge is enabled exactly once per PR). When a maintainer pushes commits to a bot's branch (a `synchronize` event with an actor that isn't the same bot), the merge-bot's `disable-auto-merge-on-maintainer-push` job fires and calls `gh pr merge --disable-auto`. The maintainer's commits stay in the PR but won't auto-merge with the bot's content; re-enable auto-merge manually (`gh pr merge --auto <PR>` or the GitHub UI) when ready.
- **Why parallel dual-target rather than develop-only with eventual flow-through:** push-distribution channels (HACS for Home Assistant integrations, Linux distros that vendor from `main`, etc.) consume `main` directly. A develop-only model would leave `main` running stale code during long-running develop features. Codegen content can also be production-critical (live API-derived data, language lists, build catalogs) rather than just sample/demo content, so both branches need fresh codegen on their own cadence.
- **Dual-target codegen + per-run state = merge conflicts.** If a generator embeds per-invocation state (timestamps, GUIDs, build IDs) and runs independently on `main` and `develop`, the two branches' outputs diverge and every `develop → main` release conflicts on the generated file. This template's `CodeGen/CodeGen.cs` demo embeds a timestamp; the codegen workflow passes `--runtime "${{ github.run_started_at }}"` to both matrix legs so they produce byte-identical output. **That `--runtime` plumbing is template hygiene only — not a codegen pattern derived projects should reproduce.** Your real generators should either be deterministic given the same inputs (preferred), or not run on both release branches simultaneously, or absorb the per-release merge cost.
- **Dual-target codegen + per-run state = merge conflicts.** If a generator embeds per-invocation state (timestamps, GUIDs, build IDs) and runs independently on `main` and `develop`, the two branches' outputs diverge and every `develop → main` release conflicts on the generated file. This template's `CodeGen/CodeGen.cs` demo embeds a timestamp; the codegen workflow captures one UTC timestamp in a `get-runtime` job and passes it via `--runtime` to both matrix legs so they produce byte-identical output. (It deliberately does *not* use `github.run_started_at`, which resolves to an empty string in the reusable-workflow context and made each leg fall back to its own `DateTime.UtcNow` — diverging the output and reintroducing the conflict.) **That `--runtime` plumbing is template hygiene only — not a codegen pattern derived projects should reproduce.** Your real generators should either be deterministic given the same inputs (preferred), or not run on both release branches simultaneously, or absorb the per-release merge cost.
- **App-token workflows use Client ID, not App ID.** `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0; the template uses `client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }}`. When adding new App-token call sites, use the same form — do not reintroduce `app-id` / `CODEGEN_APP_ID`. See [README "Template - GitHub Setup"](./README.md#template---github-setup) for the secret-setup procedure.

## Release Model
Expand Down