From bd3876e3dcf88571d69f8617e75b653efa9e3939 Mon Sep 17 00:00:00 2001 From: "ptr727-codegen[bot]" <275599072+ptr727-codegen[bot]@users.noreply.github.com> Date: Mon, 18 May 2026 03:10:55 +0000 Subject: [PATCH 1/3] Update codegen files (#86) This PR updates the codegen files. Co-authored-by: ptr727-codegen[bot] <275599072+ptr727-codegen[bot]@users.noreply.github.com> --- CodeGen/CodeGen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeGen/CodeGen.cs b/CodeGen/CodeGen.cs index a4d051e2..d811c5f9 100644 --- a/CodeGen/CodeGen.cs +++ b/CodeGen/CodeGen.cs @@ -8,7 +8,7 @@ internal static class CodeGen internal static void Quote() { - const string dateTime = "2026-04-30T03:07:13.4355622Z"; + const string dateTime = "2026-05-18T03:01:36.2172197Z"; Console.WriteLine($"{dateTime} : {QuoteOfTheDay}"); Log.Logger.Information("Quote of the Day: {DateTime} : {Quote}", dateTime, QuoteOfTheDay); } From d013abf7ef38be45ea8cb5dc9a5fa5ec63b0faf6 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 24 May 2026 12:46:43 -0700 Subject: [PATCH 2/3] Migrate App-token to client-id and drop inert codegen PR base filter (#90) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Bundles two GitHub-Actions hygiene fixes: - **Closes #88** — `actions/create-github-app-token` deprecated the numeric `app-id` input in [v3.0.0 (2026-03-14)](https://github.com/actions/create-github-app-token/releases/tag/v3.0.0) in favor of `client-id`. Bumps the SHA pin from `v1.12.0` → `v3.2.0` across all four call sites, renames secret `CODEGEN_APP_ID` → `CODEGEN_APP_CLIENT_ID`, and reworks README setup guidance to point at the App's **Client ID** (a different identifier than the numeric App ID — both are visible on the App settings page). - **Closes #89** — drops `codegen` from `test-pull-request.yml`'s `pull_request.branches` filter. The filter matches the PR's *base* ref, not the head; no PR in this repo targets a `codegen` base (codegen PRs target `main`/`develop` with head refs `codegen-main`/`codegen-develop`), so the entry was inert. Also opportunistically replaces the absolute "agents never commit" rule in `AGENTS.md` with a scope-bound authorization model: agents may commit when explicitly asked **and** signing is configured in the environment; without signing wired up they still stop at `git add`. Branch protection's signed-commit requirement is unchanged — unsigned commits are rejected on push regardless. ## Pre-merge maintainer step The `CODEGEN_APP_CLIENT_ID` secret must exist in **both** the Actions and Dependabot secret stores before merging (already provisioned). The legacy `CODEGEN_APP_ID` secret should be left in place until one round of each bot workflow runs green post-merge, then deleted. ## Test plan - [ ] CI green on this PR (`Check pull request workflow status`). - [ ] Post-merge: trigger `run-periodic-codegen-pull-request.yml` via `workflow_dispatch`; both matrix legs (`main`, `develop`) generate an App token, open a codegen PR as `ptr727-codegen[bot]`, and auto-merge. - [ ] Post-merge: next Dependabot PR exercises `merge-bot-pull-request.yml` `merge-dependabot` cleanly. - [ ] Verify `disable-auto-merge-on-maintainer-push` job still fires when a maintainer pushes to a bot PR (next-time-it-happens check). - [ ] After one green round of each bot workflow, delete legacy `CODEGEN_APP_ID` secret from both Actions and Dependabot stores. --- .github/workflows/merge-bot-pull-request.yml | 12 ++++++------ .github/workflows/run-codegen-pull-request-task.yml | 6 +++--- .github/workflows/test-pull-request.yml | 2 +- AGENTS.md | 7 +++---- README.md | 6 +++--- 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/.github/workflows/merge-bot-pull-request.yml b/.github/workflows/merge-bot-pull-request.yml index 2c274a13..cb0ed8df 100644 --- a/.github/workflows/merge-bot-pull-request.yml +++ b/.github/workflows/merge-bot-pull-request.yml @@ -77,9 +77,9 @@ jobs: - name: Generate GitHub App token step id: app-token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.CODEGEN_APP_ID }} + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Get dependabot metadata step @@ -140,9 +140,9 @@ jobs: - name: Generate GitHub App token step id: app-token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.CODEGEN_APP_ID }} + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Merge pull request step @@ -193,9 +193,9 @@ jobs: # origin, not by event actor), and the restricted GITHUB_TOKEN # is read-only. Same App token pattern as the other merge jobs. id: app-token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.CODEGEN_APP_ID }} + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Disable auto-merge step diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index daf3d675..9c139445 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -13,7 +13,7 @@ on: workflow_call: secrets: # GitHub App credentials to generate an installation token - CODEGEN_APP_ID: + CODEGEN_APP_CLIENT_ID: required: true CODEGEN_APP_PRIVATE_KEY: required: true @@ -48,9 +48,9 @@ jobs: # guard), which previously required a close/reopen dance under a PAT # to nudge the auto-merge workflow — that dance is gone. id: app-token - uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0 + uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0 with: - app-id: ${{ secrets.CODEGEN_APP_ID }} + client-id: ${{ secrets.CODEGEN_APP_CLIENT_ID }} private-key: ${{ secrets.CODEGEN_APP_PRIVATE_KEY }} - name: Setup .NET SDK step diff --git a/.github/workflows/test-pull-request.yml b/.github/workflows/test-pull-request.yml index dd87da69..53e1d628 100644 --- a/.github/workflows/test-pull-request.yml +++ b/.github/workflows/test-pull-request.yml @@ -2,7 +2,7 @@ name: Test pull request action on: pull_request: - branches: [ main, develop, codegen ] + branches: [ main, develop ] workflow_dispatch: concurrency: diff --git a/AGENTS.md b/AGENTS.md index 4f001e4a..b753db3b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,12 +9,10 @@ Treat this file as authoritative for everything else; don't restate its rules el ## Git and Commit Rules -**These rules are absolute — no exceptions:** - -- **Never make git commits.** AI coding agents cannot produce cryptographically signed commits. All commits must be signed (SSH/GPG) and must be made by the developer. Stage changes with `git add` and leave the commit to the developer. +- **Default to staging, not committing.** Stage changes with `git add` and leave `git commit` to the developer unless the developer has explicitly authorized the agent to commit for the current ask ("commit this", "open a PR", etc.). Authorization is scope-bound — it covers the commits needed for that specific task, not a blanket commit license for the rest of the session. +- **All commits must be cryptographically signed (SSH or GPG).** Branch protection enforces this on both branches; unsigned commits are rejected on push. Signing depends on environment configuration — `git config commit.gpgsign true`, a configured `user.signingkey`, and a working signing agent (loaded `ssh-agent` for SSH, or `gpg-agent` for GPG). If signing is not configured in the environment, **do not commit** — surface the missing config to the developer and stop at `git add`. Verify before any agent-authored commit (`git config --get commit.gpgsign && ssh-add -L` or the GPG equivalent). - **Never force push.** Do not run `git push --force` or `git push --force-with-lease` under any circumstances. Force pushing rewrites shared history and can cause data loss. - **Never run destructive git commands** (`git reset --hard`, `git checkout .`, `git restore .`, `git clean -f`) without explicit developer instruction. -- **Staging is the limit.** Prepare and stage file changes; the developer runs `git commit` in their own environment where signing keys are available. ## Branching Model @@ -29,6 +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 ` 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. +- **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. ## Pull Request Title and Commit Message Conventions diff --git a/README.md b/README.md index 5ea67df0..bac4755f 100644 --- a/README.md +++ b/README.md @@ -447,9 +447,9 @@ Licensed under the [MIT License][license-link]\ - Contents: Read & write — push commits to the `codegen` branch and merge bot PRs. - Pull requests: Read & write — open, update, and merge pull requests. - Metadata: Read-only (auto-required). - - Note the App ID from the app's settings page; generate a private key (downloads a `.pem` file). + - Note the **Client ID** from the app's settings page (labeled "Client ID" directly under the App name on the General tab — it looks like `Iv23li...`; **not** the numeric App ID shown above it). `actions/create-github-app-token` deprecated the numeric `app-id` input in v3.0.0 in favor of `client-id`. Also generate a private key (downloads a `.pem` file). - [Install the app](https://github.com/settings/apps) on your account and grant it access to the repository. The app must be both created **and** installed — creating it alone is not sufficient (`actions/create-github-app-token` fails with `Not Found` if the app isn't installed on the repository). - - Save the App ID as `CODEGEN_APP_ID` and the private key contents as `CODEGEN_APP_PRIVATE_KEY` in **both** of: + - Save the Client ID as `CODEGEN_APP_CLIENT_ID` and the private key contents as `CODEGEN_APP_PRIVATE_KEY` in **both** of: - GitHub project security Settings / Secrets / Actions — for the codegen workflow and the codegen merge job. - GitHub project security Settings / Secrets / Dependabot — **required** because Dependabot-triggered `pull_request` workflow runs use a separate, restricted secret context that doesn't see Actions secrets. Without the App secrets in the Dependabot store, the `merge-dependabot` job in `merge-bot-pull-request.yml` can't mint an App token and the PR will never auto-merge. - If the codegen workflows require additional secrets (e.g. third-party API keys), register them in the Actions store; if a Dependabot-triggered workflow ever needs them, register them in the Dependabot store too. @@ -465,7 +465,7 @@ Licensed under the [MIT License][license-link]\ **Codegen workflow schedule**: -- `run-periodic-codegen-pull-request.yml` runs every **Monday** at 02:00 UTC, plus on-demand via `workflow_dispatch`. It uses the App token (`CODEGEN_APP_ID` + `CODEGEN_APP_PRIVATE_KEY`) to commit, open the PR as `ptr727-codegen[bot]`, and let the merge-bot auto-merge once CI passes. No PAT, no close/reopen dance. +- `run-periodic-codegen-pull-request.yml` runs every **Monday** at 02:00 UTC, plus on-demand via `workflow_dispatch`. It uses the App token (`CODEGEN_APP_CLIENT_ID` + `CODEGEN_APP_PRIVATE_KEY`) to commit, open the PR as `ptr727-codegen[bot]`, and let the merge-bot auto-merge once CI passes. No PAT, no close/reopen dance. **GitHub project settings**: From 9c1f39e442430965801c1120483cab2b704cd0c2 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Sun, 24 May 2026 17:07:49 -0700 Subject: [PATCH 3/3] Pass shared run timestamp into CodeGen to prevent dual-target merge conflicts (#92) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Eliminates the recurring `develop → main` merge conflict on `CodeGen/CodeGen.cs`. PR #91 surfaced the issue: the codegen matrix's two legs (main, develop) each embed their own `DateTime.UtcNow:o`, so the generated file diverges every codegen cycle and every release merge conflicts on the same line. The conflict also blocks GitHub from computing the PR merge ref, which is why no `pull_request` workflow could fire on PR #91. ## Changes - **CodeGen project**: new optional `--runtime` / `-r` CLI flag. When set, the value is embedded verbatim as the file's `dateTime` constant; when omitted, behavior is unchanged (`DateTime.UtcNow.ToString("o")`). [`CommandLine.cs`](CodeGen/CommandLine.cs), [`CodeGenBuilder.cs`](CodeGen/CodeGenBuilder.cs), [`Program.cs`](CodeGen/Program.cs). - **Codegen workflow**: passes `--runtime "${{ github.run_started_at }}"` so both matrix legs in a single invocation receive the same string and emit byte-identical `CodeGen.cs`. [`.github/workflows/run-codegen-pull-request-task.yml`](.github/workflows/run-codegen-pull-request-task.yml). - **`CodeGen/CodeGen.cs`**: pin develop's timestamp to main's already-released value, so the *currently-open* PR #91 unblocks and the next codegen run starts both branches from the same string. - **`AGENTS.md`**: documents the dual-target-codegen + per-run-state pitfall and labels the `--runtime` plumbing as template-internal hygiene, not a generator pattern derived projects should reproduce. ## Scope note (per the user) Per-run-unique output from a codegen demo is **expected**. The `--runtime` flag exists *only* because this template runs the same demo generator on two release branches in parallel; passing a shared timestamp keeps releases from blocking on the resulting textual conflicts. Derived projects' real generators should be deterministic given the same inputs, or be scoped to a single release branch, or accept the per-release merge cost — **not** copy this `--runtime` plumbing. Inline comments at [CodeGenBuilder.cs:7-13](CodeGen/CodeGenBuilder.cs#L7-L13), [CommandLine.cs:67-73](CodeGen/CommandLine.cs#L67-L73), and [run-codegen-pull-request-task.yml:67-74](.github/workflows/run-codegen-pull-request-task.yml#L67-L74) repeat this so each touchpoint is self-explanatory. ## Test plan - [ ] CI green on this PR. - [ ] Local smoke test (already verified): `dotnet run --project CodeGen/CodeGen.csproj -- --codepath /tmp/x --runtime "2026-05-24T20:00:00Z"` produces `dateTime = "2026-05-24T20:00:00Z"`; omitting `--runtime` produces a `DateTime.UtcNow:o`-style value as before. - [ ] After merge, the existing PR #91 (develop → main) refreshes to a conflict-free state, `pull_request` workflows fire on it, and it can merge cleanly. - [ ] Next scheduled `run-periodic-codegen-pull-request.yml`: both matrix legs produce identical `CodeGen/CodeGen.cs` content. --- .../run-codegen-pull-request-task.yml | 10 +++++++- AGENTS.md | 1 + CodeGen/CodeGen.cs | 2 +- CodeGen/CodeGenBuilder.cs | 15 +++++++++-- CodeGen/CommandLine.cs | 25 ++++++++++++++++++- CodeGen/Program.cs | 4 ++- 6 files changed, 51 insertions(+), 6 deletions(-) diff --git a/.github/workflows/run-codegen-pull-request-task.yml b/.github/workflows/run-codegen-pull-request-task.yml index 9c139445..f7785d99 100644 --- a/.github/workflows/run-codegen-pull-request-task.yml +++ b/.github/workflows/run-codegen-pull-request-task.yml @@ -65,11 +65,19 @@ 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. run: | set -euo pipefail dotnet run --project ./CodeGen/CodeGen.csproj -- \ --codepath ./CodeGen \ - --apikey "${{ secrets.NINJA_API_KEY }}" + --apikey "${{ secrets.NINJA_API_KEY }}" \ + --runtime "${{ github.run_started_at }}" - name: Format code step run: | diff --git a/AGENTS.md b/AGENTS.md index b753db3b..4f7ffd17 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -27,6 +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 ` 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. - **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. ## Pull Request Title and Commit Message Conventions diff --git a/CodeGen/CodeGen.cs b/CodeGen/CodeGen.cs index d811c5f9..22a57664 100644 --- a/CodeGen/CodeGen.cs +++ b/CodeGen/CodeGen.cs @@ -8,7 +8,7 @@ internal static class CodeGen internal static void Quote() { - const string dateTime = "2026-05-18T03:01:36.2172197Z"; + const string dateTime = "2026-05-18T03:01:38.0586119Z"; Console.WriteLine($"{dateTime} : {QuoteOfTheDay}"); Log.Logger.Information("Quote of the Day: {DateTime} : {Quote}", dateTime, QuoteOfTheDay); } diff --git a/CodeGen/CodeGenBuilder.cs b/CodeGen/CodeGenBuilder.cs index e0be3675..a4e19d70 100644 --- a/CodeGen/CodeGenBuilder.cs +++ b/CodeGen/CodeGenBuilder.cs @@ -4,8 +4,19 @@ namespace ptr727.ProjectTemplate.CodeGen; internal sealed class CodeGenBuilder(string outputPath, CancellationToken cancellationToken) { - internal async Task CodeGenAsync(string quote) + // `runtime` is a template-internal hook: when the dual-target codegen + // matrix passes the same value to both main and develop legs, they + // produce byte-identical CodeGen.cs and develop->main merges don't + // conflict on this file. Empty -> use DateTime.UtcNow.ToString("o") + // (the original demo behavior, kept for local `dotnet run` use). + // This plumbing exists for template hygiene only — derived projects' + // real codegen should not copy this pattern. + internal async Task CodeGenAsync(string quote, string runtime) { + string dateTime = string.IsNullOrEmpty(runtime) + ? DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture) + : runtime; + // Codegen example string codeGen = $$""" namespace ptr727.ProjectTemplate.CodeGen; @@ -17,7 +28,7 @@ internal static class CodeGen internal static void Quote() { - const string dateTime = "{{DateTime.UtcNow:o}}"; + const string dateTime = {{ToCSharpStringLiteral(dateTime)}}; Console.WriteLine($"{dateTime} : {QuoteOfTheDay}"); Log.Logger.Information("Quote of the Day: {DateTime} : {Quote}", dateTime, QuoteOfTheDay); } diff --git a/CodeGen/CommandLine.cs b/CodeGen/CommandLine.cs index 3e0bc6ed..daa32d4b 100644 --- a/CodeGen/CommandLine.cs +++ b/CodeGen/CommandLine.cs @@ -7,6 +7,7 @@ internal sealed class CommandLine { private readonly Option _codePathOption = CreateCodePathOption(); private readonly Option _apiKeyOption = CreateApiKeyOption(); + private readonly Option _runtimeOption = CreateRuntimeOption(); private static readonly FrozenSet s_cliBypassList = FrozenSet.Create( StringComparer.OrdinalIgnoreCase, @@ -25,7 +26,12 @@ internal CommandLine(string[] args) internal RootCommand CreateRootCommand() { - RootCommand rootCommand = new("C# .NET codegen project") { _codePathOption, _apiKeyOption }; + RootCommand rootCommand = new("C# .NET codegen project") + { + _codePathOption, + _apiKeyOption, + _runtimeOption, + }; rootCommand.SetAction( (parseResult, cancellationToken) => { @@ -42,6 +48,7 @@ internal Options CreateOptions(ParseResult parseResult) => { CodePath = parseResult.GetValue(_codePathOption)!, ApiKey = parseResult.GetValue(_apiKeyOption) ?? string.Empty, + Runtime = parseResult.GetValue(_runtimeOption) ?? string.Empty, }; private static Option CreateCodePathOption() @@ -57,6 +64,21 @@ private static Option CreateCodePathOption() private static Option CreateApiKeyOption() => new("--apikey", "-a") { Description = "The API key to use (optional).", Required = false }; + // Template-internal: deterministic timestamp injection so the dual-target + // codegen matrix produces byte-identical CodeGen.cs on main and develop + // (eliminates merge conflicts on every develop->main release). Derived + // projects: do NOT replicate this plumbing for production codegen — if + // your generator's per-run timestamp is intentional, accept the conflicts + // or redesign the generator. See README "Template - GitHub Setup". + private static Option CreateRuntimeOption() => + new("--runtime", "-r") + { + Description = + "Override the timestamp embedded in generated content " + + "(ISO 8601; defaults to DateTime.UtcNow).", + Required = false, + }; + internal static bool BypassStartup(ParseResult parseResult) => parseResult.Errors.Count > 0 || parseResult.CommandResult.Children.Any(symbolResult => @@ -68,5 +90,6 @@ internal sealed class Options { internal required DirectoryInfo CodePath { get; init; } internal required string ApiKey { get; init; } + internal required string Runtime { get; init; } } } diff --git a/CodeGen/Program.cs b/CodeGen/Program.cs index 15f68ec2..9ea4efe3 100644 --- a/CodeGen/Program.cs +++ b/CodeGen/Program.cs @@ -63,7 +63,9 @@ internal async Task ExecuteAsync() string outputPath = Path.Combine(commandLineOptions.CodePath.FullName, "CodeGen.cs"); Log.Information("Writing quote to {OutputPath}", outputPath); CodeGenBuilder codegenBuilder = new(outputPath, cancellationToken); - await codegenBuilder.CodeGenAsync(quoteoftheday).ConfigureAwait(false); + await codegenBuilder + .CodeGenAsync(quoteoftheday, commandLineOptions.Runtime) + .ConfigureAwait(false); return 0; }