Skip to content

Conversation

@Windows10CE
Copy link
Member

This PR makes it so Unix file mode is preserved into the resulting archive. As a result of how I rewrote the planner, it also now avoids bringing entire files into memory at a time, which should avoid issues around files being too large to fit into memory. Fixes #107.

Also updates to .NET 8.0 since net6/net7 are out of support now.

@coderabbitai
Copy link

coderabbitai bot commented Oct 17, 2025

Walkthrough

This pull request upgrades the target framework from net6.0/net7.0 to net8.0 across both ThunderstoreCLI and ThunderstoreCLI.Tests projects. In BuildCommand.cs, a new polymorphic EntryData hierarchy replaces direct byte array handling, with FromFile and FromMemory implementations providing per-entry stream access and Unix file mode metadata. The ArchivePlan class now stores configuration and error/warning state alongside entries. Several helper methods transition to private visibility, and inline OperatingSystem.IsWindows() checks replace the previous isWindows flag. Archive writing now explicitly sets file permissions via GetUnixFileMode() when creating entries.

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Preserve unix file mode for files copied into package zips" directly and clearly summarizes the primary change in the changeset. The PR introduces an EntryData hierarchy in BuildCommand.cs specifically to track and preserve Unix file modes when creating archives, using entry.GetUnixFileMode() to set ExternalAttributes during archive creation. This is the core change that addresses the linked issue (#107), and the title accurately reflects this focus without being vague or misleading.
Linked Issues Check ✅ Passed The code changes directly address the primary objective of issue #107: preserving the executable bit (Unix file mode) when building packages. The implementation introduces an EntryData abstraction with GetUnixFileMode() support and uses it when writing archive entries (entry.GetUnixFileMode() sets ExternalAttributes for non-Windows platforms). Files are now loaded via EntryData.FromFile or EntryData.FromMemory, which preserves metadata needed to maintain permissions in the resulting archive. This fulfills the requirement to prevent tcli from deleting the executable bit during the build process.
Out of Scope Changes Check ✅ Passed The core changes—introducing the EntryData hierarchy and refactoring BuildCommand.cs to preserve Unix file modes—are properly scoped to address issue #107. The helper function refactoring (making methods private) supports these main changes. The .NET 8.0 update to ThunderstoreCLI.csproj and ThunderstoreCLI.Tests.csproj is technically beyond the scope of #107 but is explicitly mentioned in the PR description as a secondary objective ("Also updates to .NET 8.0 since net6/net7 are out of support now"), making it an intentional part of this PR's stated work rather than an unrelated change.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/archive-permissions

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

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

@codecov
Copy link

codecov bot commented Oct 17, 2025

Codecov Report

❌ Patch coverage is 0% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 5.46%. Comparing base (1116dc7) to head (8aa37ad).

Files with missing lines Patch % Lines
ThunderstoreCLI/Commands/BuildCommand.cs 0.00% 39 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff            @@
##           master    #109      +/-   ##
=========================================
- Coverage    5.47%   5.46%   -0.01%     
=========================================
  Files          42      41       -1     
  Lines        2413    2416       +3     
  Branches      238     238              
=========================================
  Hits          132     132              
- Misses       2278    2281       +3     
  Partials        3       3              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Caution

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

⚠️ Outside diff range comments (1)
ThunderstoreCLI.Tests/ThunderstoreCLI.Tests.csproj (1)

11-27: Upgrade test tooling packages to support .NET 8

The test SDK and collector packages are significantly outdated. Update to latest stable versions:

  • Microsoft.NET.Test.Sdk: 17.4.1 → 18.0.0
  • xunit: 2.4.2 → 2.9.3
  • xunit.runner.visualstudio: 2.4.5 → 3.1.5
  • coverlet.collector: 3.2.0 → 6.0.4
🧹 Nitpick comments (6)
ThunderstoreCLI/Commands/BuildCommand.cs (2)

197-201: Tighten manifest default perms to 0644

0644 is a safer default for text files than 0664.

-        plan.AddPlan("manifest.json", new EntryData.FromMemory(Encoding.UTF8.GetBytes(SerializeManifest(config)), (UnixFileMode)0b110_110_100)); // rw-rw-r--
+        plan.AddPlan("manifest.json", new EntryData.FromMemory(Encoding.UTF8.GetBytes(SerializeManifest(config)), (UnixFileMode)0b110_100_100)); // rw-r--r--

220-239: Add a regression test for exec-bit preservation

To lock the fix, add a test that:

  • Creates a temp file, sets 0755, builds a zip entry, then asserts ((entry.ExternalAttributes >> 16) & 0x1FF) == 0o755.

I can draft a small xUnit test that runs on Linux/macOS CI and skips on Windows if needed.

ThunderstoreCLI.Tests/ThunderstoreCLI.Tests.csproj (2)

6-6: Align C# language version with .NET 8

Pinning LangVersion to 11 limits features and analyzers on net8. Consider removing it (use SDK default C# 12) or set to 12.

-        <LangVersion>11</LangVersion>
+        <!-- Prefer SDK default for net8 (C# 12) -->
+        <LangVersion>12</LangVersion>

13-16: Remove PolySharp in tests on net8

PolySharp is unnecessary on .NET 8; keeping it adds build time and potential analyzer noise.

-        <PackageReference Include="PolySharp" Version="1.12.1">
-          <PrivateAssets>all</PrivateAssets>
-          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-        </PackageReference>
ThunderstoreCLI/ThunderstoreCLI.csproj (2)

6-6: Use C# 12 (or remove explicit LangVersion)

For net8, prefer SDK default (C# 12). Keeping 11 may restrict newer compiler/analyzers.

-        <LangVersion>11</LangVersion>
+        <LangVersion>12</LangVersion>

36-41: Drop PolySharp on .NET 8

The comment indicates it was needed for NET 6. On net8 it’s redundant.

-        <!-- needed to use some C#11 features with NET 6, like required properties. -->
-        <PackageReference Include="PolySharp" Version="1.12.1">
-          <PrivateAssets>all</PrivateAssets>
-          <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
-        </PackageReference>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1116dc7 and 8aa37ad.

📒 Files selected for processing (3)
  • ThunderstoreCLI.Tests/ThunderstoreCLI.Tests.csproj (1 hunks)
  • ThunderstoreCLI/Commands/BuildCommand.cs (11 hunks)
  • ThunderstoreCLI/ThunderstoreCLI.csproj (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
ThunderstoreCLI/Commands/BuildCommand.cs (3)
ThunderstoreCLI/Configuration/Config.cs (4)
  • Config (8-164)
  • Config (28-32)
  • Config (33-42)
  • Config (115-143)
ThunderstoreCLI/Utils/Write.cs (2)
  • Write (5-89)
  • Error (24-27)
ThunderstoreCLI/Program.cs (2)
  • CommandException (83-86)
  • CommandException (85-85)
⏰ 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). (3)
  • GitHub Check: Build and test macOS-latest
  • GitHub Check: Build and test windows-latest
  • GitHub Check: Build and test ubuntu-latest
🔇 Additional comments (1)
ThunderstoreCLI/Commands/BuildCommand.cs (1)

230-239: I'll verify the core concerns in this review by examining the codebase context and the implementation details.

Now let me verify the specifics in the codebase:

Now let me verify the actual code in the repository to understand what entry.Value.GetUnixFileMode() returns:

Based on my research, I have verified the review comment against relevant documentation and real-world issues in the .NET ecosystem:

Summary of findings:

The ZIP file format uses high 16 bits of external file attributes to store Unix file mode, including file-type bits (e.g., 0100000 for regular files) in the top 4 bits and permissions in the lower bits.

The .NET runtime had a regression issue (#76006) where ZIP archive entries were missing file-type bits in external attributes, causing files to be inaccessible when extracted on macOS.

File.GetUnixFileMode() returns only the UnixFileMode enum, which represents filesystem permissions without file-type bits. This means the current code setting entry.Value.GetUnixFileMode() << 16 captures permissions but omits the S_IFREG (0x8000) file-type bit.

For Unix systems, the high order byte of ExternalAttributes is frequently used to store file permissions, and the standard expects both file-type and permission bits.

The review comment's core concern is valid: the code should include the S_IFREG file-type bit alongside permission bits. Both suggested options (always set, or keep the Windows gate but add the type bit) are defensible approaches to fix this. The Windows gate rationale is conservative but not unreasonable—however, many tools honor the upper 16 bits regardless of build platform, so Option A (always set) is more robust for cross-platform compatibility.

Comment on lines 266 to 270
foreach (string filename in Directory.EnumerateFiles(basePath, "*.*", SearchOption.AllDirectories))
{
var targetPath = FormatArchivePath($"{destDirectory}{filename[(basePath.Length + 1)..]}");
plan.AddPlan(targetPath, () => File.ReadAllBytes(filename));
plan.AddPlan(targetPath, new EntryData.FromFile(filename));
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix: directory file search skips files without extensions

Using "." will miss files like "script", "LICENSE", etc. Use "*" to include all files.

Apply:

-            foreach (string filename in Directory.EnumerateFiles(basePath, "*.*", SearchOption.AllDirectories))
+            foreach (string filename in Directory.EnumerateFiles(basePath, "*", SearchOption.AllDirectories))
📝 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
foreach (string filename in Directory.EnumerateFiles(basePath, "*.*", SearchOption.AllDirectories))
{
var targetPath = FormatArchivePath($"{destDirectory}{filename[(basePath.Length + 1)..]}");
plan.AddPlan(targetPath, () => File.ReadAllBytes(filename));
plan.AddPlan(targetPath, new EntryData.FromFile(filename));
}
foreach (string filename in Directory.EnumerateFiles(basePath, "*", SearchOption.AllDirectories))
{
var targetPath = FormatArchivePath($"{destDirectory}{filename[(basePath.Length + 1)..]}");
plan.AddPlan(targetPath, new EntryData.FromFile(filename));
}
🤖 Prompt for AI Agents
In ThunderstoreCLI/Commands/BuildCommand.cs around lines 266 to 270, the
Directory.EnumerateFiles call uses the search pattern "*.*" which skips files
without extensions (e.g., LICENSE, script); change the pattern to "*" so all
files are included, keep SearchOption.AllDirectories and the rest of the loop
logic the same, and ensure the targetPath computation and plan.AddPlan call
remain unchanged.

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.

tcli deletes executable bit when building

2 participants