Skip to content

feat : auto-generate manifest.json by CI - #54

Merged
r1ckstardev merged 10 commits into
btcpayserver:mainfrom
teamssUTXO:main
Apr 29, 2026
Merged

feat : auto-generate manifest.json by CI#54
r1ckstardev merged 10 commits into
btcpayserver:mainfrom
teamssUTXO:main

Conversation

@teamssUTXO

Copy link
Copy Markdown
Collaborator

This PR tracks Phase 1 of the BTCPay Server translations revamp roadmap.

Changes

  • ManifestGenerator service : build and write the manifest file
  • CLI Command generate-manifest : (dotnet run -- generate-manifest)
  • CI workflow : triggers on push to main when a translation file changes + runs generate-manifest command, commits and pushes manifest.json
  • Test suite : covers ManifestGenerator service

@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 58ba0a69-cb24-4a17-9960-8c767747cdda

📥 Commits

Reviewing files that changed from the base of the PR and between ebc247f and f24efb3.

📒 Files selected for processing (4)
  • .github/workflows/manifest.yml
  • Translator/Models/LanguageInfo.cs
  • Translator/Program.cs
  • Translator/Services/ManifestGenerator.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • .github/workflows/manifest.yml
  • Translator/Program.cs

📝 Walkthrough

Walkthrough

Adds a manifest generation system: new GitHub Actions workflow, CLI subcommand, ManifestGenerator service, manifest data models, and tests to produce and update manifest.json from translation JSON files (including SHA, maintainer, and ISO-8601 UTC timestamps).

Changes

Cohort / File(s) Summary
CI / Workflow
.github/workflows/manifest.yml
New workflow Generate Manifest that builds the project, runs the generate-manifest CLI, and commits manifest.json on translation changes or manual dispatch.
CLI / Bootstrapping
Translator/Program.cs
Registers ManifestGenerator, adds generate-manifest subcommand with --translation-path and --manifest-path options and exit-code handling.
Service / Core Logic
Translator/Services/ManifestGenerator.cs
New ManifestGenerator that scans translations/*.json, computes SHA-256, extracts _maintainer, resolves language info, preserves per-file Updated when SHA unchanged, and writes manifest.json (indented, relaxed escaping). Returns false on error or no files.
Models
Translator/Models/ManifestEntry.cs, Translator/Models/LanguageInfo.cs
Adds ManifestEntry and Manifest record types; adds SupportedLanguages.GetLanguageInfoByName(string) to resolve language info by human-readable name.
Tests
Translator.Tests/Services/ManifestGeneratorTests.cs
New xUnit tests covering valid manifest creation, missing/invalid translation scenarios, SHA/update preservation, maintainer absent behavior, and error cases.
Translations
Translator/translations/french.json
Minor wording change for the French translation of "Your node address: {0}".

Sequence Diagram

sequenceDiagram
    participant Workflow as GitHub Actions
    participant CLI as Program CLI
    participant Gen as ManifestGenerator
    participant FS as File System
    participant LangMap as SupportedLanguages

    Workflow->>CLI: run `generate-manifest` (manual or on push)
    CLI->>Gen: instantiate via DI
    CLI->>Gen: GenerateManifest(translationPath, manifestPath)

    Gen->>FS: Enumerate translation files (translations/*.json)
    FS-->>Gen: file list

    loop per file
        Gen->>LangMap: GetLanguageInfoByName(filenameWithoutExt)
        LangMap-->>Gen: (code, LanguageInfo) or null
        Gen->>FS: Read file content
        FS-->>Gen: JSON content
        Gen->>Gen: compute SHA-256
        Gen->>Gen: parse `_maintainer` field (if any)
        Gen->>Gen: build ManifestEntry (File = translations/<name>.json)
    end

    Gen->>FS: Read existing manifest.json (if exists)
    FS-->>Gen: existing manifest

    alt existing entry SHA matches
        Gen->>Gen: preserve existing Updated
    else
        Gen->>Gen: set Updated = run UTC timestamp
    end

    Gen->>FS: Write manifest.json (indented, relaxed escaping)
    FS-->>Gen: write result
    Gen-->>CLI: return success/failure
    CLI->>Workflow: exit code
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
Hopping through JSON, I sniff each line,
SHA stamped and tidy, each language in fine,
Timestamps set in UTC, maintainer noted too,
CI plants the manifest — a carrot for you! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: auto-generation of manifest.json via CI. It directly corresponds to the primary objective of adding automated manifest generation.
Description check ✅ Passed The description is well-structured and clearly related to the changeset, documenting the four key additions: ManifestGenerator service, CLI command, CI workflow, and test suite.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
Translator/Services/ManifestGenerator.cs (1)

132-147: Make manifest entry ordering deterministic.

Current iteration order depends on filesystem enumeration. Sorting before building entries avoids nondeterministic manifest.json churn.

♻️ Proposed refactor
-            var files = GetTranslationFiles(translationDirectoryPath)?.ToArray();
+            var files = GetTranslationFiles(translationDirectoryPath)?
+                .OrderBy(Path.GetFileName, StringComparer.OrdinalIgnoreCase)
+                .ToArray();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Translator/Services/ManifestGenerator.cs` around lines 132 - 147, The
manifest generation is nondeterministic because filesystem enumeration order is
used; before iterating call GetTranslationFiles(translationDirectoryPath) and
sort the resulting file list (e.g., OrderBy(Path.GetFileName,
StringComparer.OrdinalIgnoreCase) or another stable key) so the loop that calls
BuildEntry(file, existingEntry) always processes files in a deterministic order;
update the loop that populates entries (and any variable like files) to use the
sorted sequence so manifest.json churn is eliminated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Translator.Tests/CliTestHost.cs`:
- Line 65: The timeout Task.Delay is using cts.Token which may already be
canceled so the delay completes immediately; change the call in the awaiting
expression that combines stdOutTask and stdErrTask (the Task.WhenAny awaiting
Task.WhenAll(stdOutTask, stdErrTask), Task.Delay(...)) to use a non-cancelable
token (e.g., CancellationToken.None) or a fresh CancellationToken that isn’t
already cancelled so the 2000ms grace period actually waits for stream drain
instead of being bypassed.

---

Nitpick comments:
In `@Translator/Services/ManifestGenerator.cs`:
- Around line 132-147: The manifest generation is nondeterministic because
filesystem enumeration order is used; before iterating call
GetTranslationFiles(translationDirectoryPath) and sort the resulting file list
(e.g., OrderBy(Path.GetFileName, StringComparer.OrdinalIgnoreCase) or another
stable key) so the loop that calls BuildEntry(file, existingEntry) always
processes files in a deterministic order; update the loop that populates entries
(and any variable like files) to use the sorted sequence so manifest.json churn
is eliminated.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f6d6b33e-ae00-4145-a776-81d566a76fe2

📥 Commits

Reviewing files that changed from the base of the PR and between 45850a0 and 40e9f3e.

📒 Files selected for processing (8)
  • .github/workflows/manifest.yml
  • Translator.Tests/CliTestHost.cs
  • Translator.Tests/Services/ManifestGeneratorTests.cs
  • Translator/Models/LanguageInfo.cs
  • Translator/Models/ManifestEntry.cs
  • Translator/Program.cs
  • Translator/Services/ManifestGenerator.cs
  • Translator/translations/french.json

Comment thread Translator.Tests/CliTestHost.cs Outdated
teamssUTXO and others added 2 commits April 29, 2026 21:16
Per @j3nnystar_bot review (btcpayserver#54),
@teamssUTXO triaged 8 findings; this commit ships btcpayserver#1 + btcpayserver#2 + btcpayserver#3 + btcpayserver#5 +
btcpayserver#6 + btcpayserver#7 + btcpayserver#8.

btcpayserver#1 Per-file timestamp drift inside one run.
   ManifestGenerator now captures `runUpdatedAt` once at the top of
   GenerateManifest and threads it into BuildEntry. All files changed
   in a single run share one Updated stamp instead of drifting per
   file as the run progresses.

btcpayserver#2 File-name to language coupling - solution B (explicit mapping).
   Models/LanguageInfo.cs adds an explicit `FileNameToCode` dictionary
   mapping translation-file basenames (`french`, `german`, etc.) to
   Languages keys. New `GetLanguageInfoByFileName` replaces the old
   `GetLanguageInfoByName` Name-equality lookup. The contract is now
   visible: a new translation file requires a row here, which is
   intentional - implicit Name lookup could silently mis-route or
   drop entries (e.g. `portuguese.json` -> "Portuguese (Brazil)" by
   parenthetical accident).

btcpayserver#3 Fail-fast on per-file failure: kept, plus manual recovery trigger.
   .github/workflows/manifest.yml gains `workflow_dispatch:` so a
   broken translation file can be fixed and the manifest re-generated
   from the Actions tab without waiting for another translations push.
   ManifestGenerator's foreach has a comment explicitly naming the
   fail-fast posture and pointing at the dispatch trigger as the
   recovery path.

btcpayserver#5 GetMaintainer async hygiene.
   Swapped sync `File.ReadAllText` for `await File.ReadAllTextAsync`,
   made the helper return `Task<string?>`, awaited at the call site.

btcpayserver#6 Whitespace nit in HashFiles.
   Single space between `await` and `sha256.ComputeHashAsync`.

btcpayserver#7 Default paths anchored to project directory (per @teamssUTXO go-ahead).
   New `ResolveProjectDirectory()` helper walks up from
   `AppContext.BaseDirectory` looking for `BTCPayTranslator.csproj`.
   `--translation-path` defaults to `<project-dir>/translations`,
   `--manifest-path` defaults to `<repo-root>/manifest.json`. The
   command produces the same manifest whether invoked from the repo
   root, from inside Translator/, or from the workflow with its
   explicit `working-directory: Translator`.

btcpayserver#8 Rename `CreateManifest` -> `CreateGenerateManifestCommand`.
   Matches the symmetry of `CreateUpdateCommand`,
   `CreateValidatePacksCommand`, etc.

btcpayserver#4 (existing translation files lacking `_maintainer`) was triaged out
   of scope for this PR per @teamssUTXO.

Local verification: dotnet build clean (0/0 errors, 0 warnings),
dotnet test 40/40 passing on .NET 10 RC.2 (`10.0.100-rc.2.25502.107`).

Co-Authored-By: Timothé <183613235+teamssUTXO@users.noreply.github.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
Translator/Services/ManifestGenerator.cs (1)

31-35: Avoid wrapping with generic Exception; preserve original exception context.

On Line 31, Line 47, Line 69, and Line 117, catching and rethrowing new Exception(...) adds noise and loses precise exception typing for callers/log pipelines. Prefer throw; after logging or let exceptions bubble.

Also applies to: 47-51, 69-73, 117-121

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Translator/Services/ManifestGenerator.cs` around lines 31 - 35, The catch
blocks in ManifestGenerator.cs that currently do "_logger.LogError(ex, ...)"
followed by "throw new Exception(..., ex)" (seen in the catch(Exception ex)
handlers around the translation file handling and the other three locations) are
wrapping and losing the original exception type; replace each "throw new
Exception(..., ex)" with a plain "throw;" (or remove the catch entirely if
logging is redundant) so the original exception and stack trace are preserved
while keeping the existing _logger.LogError(...) calls (search for the catch
blocks that reference "_logger.LogError(ex, \"Couldn't find translation files in
{Directory}\", translationDirectoryPath)" and the other similar catch handlers
and update them to rethrow with "throw;").
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/manifest.yml:
- Line 7: The workflow path filter currently uses the non-recursive glob
'Translator/translations/**.json' which can miss nested JSONs; update the path
pattern to the explicit recursive glob 'Translator/translations/**/*.json' so
files in subdirectories are matched reliably (replace the existing
'Translator/translations/**.json' entry in the manifest with
'Translator/translations/**/*.json').
- Around line 41-47: Replace the mutable tag "uses: EndBug/add-and-commit@v10"
with an immutable full-length commit SHA for the EndBug/add-and-commit action;
locate the uses line (uses: EndBug/add-and-commit@v10) and update it to the
corresponding full commit SHA (e.g., uses:
EndBug/add-and-commit@<full-commit-sha>) so the workflow is pinned to an exact
commit per GitHub security guidelines, and verify the SHA matches the intended
release before committing.

In `@Translator/Services/ManifestGenerator.cs`:
- Around line 141-161: The list of translation files returned by
GetTranslationFiles can be non-deterministic (Directory.GetFiles ordering), so
sort the files before building entries to ensure deterministic manifest output;
after obtaining files (the files variable from GetTranslationFiles) sort them
(e.g., by full path or filename) before the foreach that calls BuildEntry(file,
existingEntry, runUpdatedAt) so ManifestEntry order is stable across runs.

---

Nitpick comments:
In `@Translator/Services/ManifestGenerator.cs`:
- Around line 31-35: The catch blocks in ManifestGenerator.cs that currently do
"_logger.LogError(ex, ...)" followed by "throw new Exception(..., ex)" (seen in
the catch(Exception ex) handlers around the translation file handling and the
other three locations) are wrapping and losing the original exception type;
replace each "throw new Exception(..., ex)" with a plain "throw;" (or remove the
catch entirely if logging is redundant) so the original exception and stack
trace are preserved while keeping the existing _logger.LogError(...) calls
(search for the catch blocks that reference "_logger.LogError(ex, \"Couldn't
find translation files in {Directory}\", translationDirectoryPath)" and the
other similar catch handlers and update them to rethrow with "throw;").
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d9ed2866-fee6-44a0-9fcf-f335dac4700d

📥 Commits

Reviewing files that changed from the base of the PR and between 40e9f3e and ebc247f.

📒 Files selected for processing (4)
  • .github/workflows/manifest.yml
  • Translator/Models/LanguageInfo.cs
  • Translator/Program.cs
  • Translator/Services/ManifestGenerator.cs

Comment thread .github/workflows/manifest.yml Outdated
Comment on lines +41 to +47
uses: EndBug/add-and-commit@v10
with:
default_author: github_actor
add: ./manifest.json # if the working directory is the repo root dir
message: "Update manifest.json"
commit: ""
push: true No newline at end of file

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What do GitHub’s official security hardening guidelines recommend for pinning third-party actions (tag vs full-length commit SHA)?

💡 Result:

GitHub's official security hardening guidelines recommend pinning third-party actions to a full-length commit SHA as the most secure option. This is the only way to use an action as an immutable release, mitigating risks from malicious updates since it requires a SHA-1 collision to compromise. They warn against short SHAs due to ambiguity risks from forks. Pinning to a tag is acceptable only if you fully trust the creator (e.g., verified badge on Marketplace), but tags are mutable and can be moved or deleted by repository access compromise. GitHub provides repository/organization policies to enforce full-length SHA pinning.

Citations:


🏁 Script executed:

# Check if the file exists and verify the content at lines 41-47
if [ -f .github/workflows/manifest.yml ]; then
  echo "=== File exists. Showing lines 35-50 for context ==="
  sed -n '35,50p' .github/workflows/manifest.yml
else
  echo "File not found at .github/workflows/manifest.yml"
  # Try to find it
  find . -name "manifest.yml" -type f 2>/dev/null | head -20
fi

Repository: btcpayserver/btcpayserver-translator

Length of output: 547


🌐 Web query:

What is the current commit SHA for EndBug/add-and-commit v10 release on GitHub

💡 Result:

The current commit SHA for the EndBug/add-and-commit v10 release (v10.0.0) on GitHub is 290ea2c423ad77ca9c62ae0f5b224379612c0321. This is confirmed as the commit tagged with v10.0.0 on March 22, 2026, which bumps the version from 9.1.4 to 10.0.0 in package.json and package-lock.json. There is also a tag "v10" pointing to the same commit. No newer v10 releases exist as of 2026-04-29.

Citations:


🏁 Script executed:

# Search for all 'uses:' directives in the manifest.yml file
grep -n "uses:" .github/workflows/manifest.yml

Repository: btcpayserver/btcpayserver-translator

Length of output: 201


Pin third-party GitHub Action by commit SHA to reduce supply-chain risk.

On Line 41, EndBug/add-and-commit@v10 uses a mutable tag. Pin to a full-length commit SHA per GitHub's official security hardening guidelines, which recommend this as the only way to use an action as an immutable release.

Proposed fix
-        uses: EndBug/add-and-commit@v10
+        uses: EndBug/add-and-commit@290ea2c423ad77ca9c62ae0f5b224379612c0321
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/manifest.yml around lines 41 - 47, Replace the mutable tag
"uses: EndBug/add-and-commit@v10" with an immutable full-length commit SHA for
the EndBug/add-and-commit action; locate the uses line (uses:
EndBug/add-and-commit@v10) and update it to the corresponding full commit SHA
(e.g., uses: EndBug/add-and-commit@<full-commit-sha>) so the workflow is pinned
to an exact commit per GitHub security guidelines, and verify the SHA matches
the intended release before committing.

Comment thread Translator/Services/ManifestGenerator.cs Outdated

@r1ckstardev r1ckstardev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approved. Build clean, tests 40/40 on .NET 10 RC.2. Issue #52 acceptance criteria met (15 languages, BCP47 / native / file / sha / maintainer / updated fields, manifest.yml workflow with workflow_dispatch trigger). Stack tells a clean story: tests added, manifest generator built with FileNameToCode dictionary + project-anchored defaults via ResolveProjectDirectory, follow-up addressed timestamp drift + async maintainer fetch + workflow_dispatch. Thanks Tim.

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.

2 participants