diff --git a/.github/agents/code-reviewer.md b/.github/agents/code-reviewer.md new file mode 100644 index 00000000..7859d0b2 --- /dev/null +++ b/.github/agents/code-reviewer.md @@ -0,0 +1,44 @@ +# Code Review Agent — PackageUploader + +You are a code reviewer for the PackageUploader repository, a .NET 8.0 solution that uploads game packages to Microsoft Partner Center. + +## What to Review + +Focus on issues that genuinely matter — bugs, security vulnerabilities, logic errors, pattern violations. Do NOT comment on style, formatting, or trivial matters. + +## Repo-Specific Rules + +### Architecture +- **ClientApi** is the shared service layer — changes here affect both CLI and UI apps +- CLI operations each have Operation + Config + JSON template + schema — verify all are updated together +- UI follows MVVM — ViewModels must inherit BaseViewModel, commands use RelayCommand + +### Security +- No secrets or credentials in code — auth uses Azure.Identity credential providers +- `Directory.Build.props` enforces `HighEntropyVA=true` and `Features=strict` — do not weaken +- NuGet packages come from private feed (XboxPackageUploaderFeed) — verify no public feed leakage + +### Error Handling +- Public API methods must throw typed exceptions from the `IngestionClientException` hierarchy +- Use `StringArgumentException.ThrowIfNullOrWhiteSpace()` for argument validation +- HTTP error handling uses catch-when filters for status codes + +### Patterns +- DI registration via extension methods on IServiceCollection +- Configuration uses IOptions with `[OptionsValidator]` partial classes and `[Required]` annotations +- HTTP clients use Polly retry with `DecorrelatedJitterBackoffV2` +- All async methods accept `CancellationToken` +- Logging uses ILogger with structured named parameters + +### Testing +- New code should have corresponding tests (MSTest + Moq) +- WPF tests require `[WpfTestMethod]` attribute +- Integration tests suffixed with `IntegrationTest` + +## Output Format + +For each issue found, provide: +1. **File and line** — exact location +2. **Severity** — 🔴 Bug, 🟡 Warning, 🔵 Suggestion +3. **What's wrong** — one sentence +4. **How to fix** — concrete recommendation diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..a67f16ef --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,130 @@ +# PackageUploader + +Cross-platform CLI tool and WPF desktop app for uploading game packages to Microsoft Partner Center. Built with .NET 8.0. + +## Architecture + +Two deliverables from one solution: +- **PackageUploader.Application** — CLI for CI/CD automation (System.CommandLine + Microsoft.Extensions.Hosting) +- **PackageUploader.UI** — WPF MVVM desktop app (Xbox Game Package Manager) + +Shared libraries: +- **PackageUploader.ClientApi** — Service layer wrapping Partner Center Ingestion API + Xfus upload service +- **PackageUploader.FileLogger** — Custom async file-based ILoggerProvider with SimpleFile and JSON formatters + +## Build & Test + +All commands run from `./src`: + +``` +dotnet restore # Restore (uses private NuGet feed via NuGet.config) +dotnet build --no-restore # Build all projects +dotnet test --no-build --verbosity normal # Run all tests (MSTest + Moq) +``` + +Publish: +``` +dotnet publish src/PackageUploader.Application/PackageUploader.Application.csproj --self-contained -r win-x64 -c release +dotnet publish src/PackageUploader.Application/PackageUploader.Application.csproj --self-contained -r linux-x64 -c release +``` + +## Project Structure + +``` +src/ + PackageUploader.Application/ CLI entry point, operations, config classes + Operations/ One class per CLI command (inherits Operation base) + Config/ Strongly-typed config per operation + Extensions/ DI registration via extension methods + PackageUploader.ClientApi/ Partner Center API wrapper + Client/Ingestion/ REST client for Ingestion API + Client/Xfus/ File upload client + Models/ Shared data models and config interfaces + PackageUploader.UI/ WPF desktop app (MVVM) + View/ XAML + code-behind pairs (code-behind only sets DataContext) + ViewModel/ ICommand properties + RelayCommand logic (all button/click handling) + Converters/ IValueConverter implementations + Providers/ Service providers for view layer + PackageUploader.FileLogger/ Custom logging provider + Formatters/ SimpleFileFormatter, JsonFileFormatter +schemas/ JSON schema for operation configs +templates/ JSON config templates per operation +``` + +## Key Patterns + +- **DI:** Register services via extension methods on IServiceCollection (e.g., `AddPackageUploaderService()`, `AddIngestionAuthentication()`) +- **Configuration:** IOptions with `[OptionsValidator]` partial classes and `[Required]` data annotations; bound via `BindConfiguration()` +- **Error handling:** Custom exception hierarchy from `IngestionClientException`; catch-when filters for HTTP status codes; `StringArgumentException.ThrowIfNullOrWhiteSpace()` for argument validation +- **Logging:** ILogger injection; structured logging with named parameters; timestamp format `"yyyy-MM-dd HH:mm:ss.fff"` +- **HTTP resilience:** Polly with `DecorrelatedJitterBackoffV2` retry + timeout policies via `AddPolicyHandler()` +- **Auth:** Strategy pattern — 11 credential providers (AppSecret, AppCert, Browser, AzureCli, ManagedIdentity, AzurePipelines, etc.) +- **UI:** MVVM — View (.xaml) binds buttons via `Command="{Binding ...}"` → code-behind (.xaml.cs) only sets DataContext → ViewModel declares `ICommand` properties with `RelayCommand` lambdas. Never put logic in code-behind. + +## Testing + +- Framework: MSTest with `[TestClass]`/`[TestMethod]` attributes +- Mocking: Moq for dependency injection +- Pattern: Arrange/Act/Assert with `[TestInitialize]`/`[TestCleanup]` +- WPF tests use custom `[WpfTestMethod]` attribute (enforces STA thread) +- Integration tests suffixed with `IntegrationTest` + +## Package Formats & Tooling + +Three package formats are supported: + +| Format | Platform | Creation Tool | Upload Method | +|--------|----------|---------------|---------------| +| **XVC** | Xbox console | MakePkg.exe | PackageUploader / Xfus | +| **UWP/MSIXVC** | Windows PC | MakePkg.exe | PackageUploader / Xfus | +| **MSIXVC2** | Next-gen (console + PC) | `makepkg2` | `makepkg2 upload` or loose-file upload | + +### makepkg2 (MSIXVC2 tooling) + +`makepkg2` is the next-gen packaging tool replacing `MakePkg.exe` for MSIXVC2 packages. +- Distributed as NuGet: `Microsoft.Xbox.Packaging.Tools.makepkg2` +- Built on .NET 10.0 (win-x64) +- Library: `Microsoft.Xbox.Packaging` — entry interface `IPackagingOperations` (impl: `PackagingOperations`) + +**Key CLI commands:** +``` +makepkg2 pack /msixvc2 /f layout.xml /d /pd [/encrypt] [/compress brotli] [/noembed] [/skipvalidation] +makepkg2 upload /msixvc2 [/auth Browser] [/branch ...] +packageutil2 extract /package .msixvc /out +``` + +**MSIXVC2 vs legacy MakePkg:** +- Supports **loose-file upload** (no packaging step required) or packaged upload (`-UsePackage`) +- **Brotli compression** and **encryption** support +- **Embedded vs non-embedded** modes (`/noembed` — boxes stored separately) +- Output: `.msixvc` files (internally zip archives containing `.box` files) +- Auth forwarded via `/auth` flag (Browser, AzureCli, etc.) +- Delta uploads between versions (V1→V2) built into `makepkg2 upload` +- Uses `layout.xml` for chunk organization (same concept as legacy) + +## Domain Glossary + +- **Partner Center** — Microsoft portal for game/app publishing +- **Product** — Game or app (identified by ProductId or BigId) +- **Branch** — Version track (branchFriendlyName) +- **Flight** — Preview/test release track (flightName) +- **Market Group** — Regional grouping for package availability +- **XVC** — Xbox Virtual Container (console package format, created by MakePkg.exe) +- **UWP/MSIXVC** — Universal Windows / Modern Xbox package formats (created by MakePkg.exe) +- **MSIXVC2** — Next-gen package format (created by makepkg2), supports loose-file upload and brotli compression +- **makepkg2** — Next-gen CLI tool for creating and uploading MSIXVC2 packages (replaces MakePkg.exe) +- **Xfus** — File upload service used by Partner Center (legacy upload path) +- **Ingestion** — Partner Center backend API +- **EKB** — Encryption key bundle (XVC asset) +- **SubVal** — Submission validation file (XVC asset) +- **Box** — GUID-named `.box` file, the processing unit within an MSIXVC2 package +- **layout.xml** — Chunk organization file defining how content maps into package chunks +- **IPackagingOperations** — Entry interface in `Microsoft.Xbox.Packaging` library for package operations + +## Key Rules + +- Security features enforced in Directory.Build.props: `HighEntropyVA=true`, `Features=strict` +- NuGet packages come from a private Azure DevOps feed (XboxPackageUploaderFeed) — see `src/NuGet.config` +- CLI operations each have their own Operation class, Config class, and JSON template — keep them in sync +- Config file reference → see Operations.md and schemas/PackageUploaderOperationConfigSchema.json +- MSIXVC2 packaging uses `makepkg2` (not legacy MakePkg.exe) — see `Microsoft.Xbox.Packaging.Tools.makepkg2` NuGet diff --git a/.github/instructions/application.instructions.md b/.github/instructions/application.instructions.md new file mode 100644 index 00000000..be210ae0 --- /dev/null +++ b/.github/instructions/application.instructions.md @@ -0,0 +1,26 @@ +--- +applyTo: "src/PackageUploader.Application/**" +--- + +# Application — CLI Entry Point + +## Overview + +Console application using System.CommandLine + Microsoft.Extensions.Hosting. Each CLI command maps to an Operation class. + +## Adding a New Operation + +1. Create a config class in `Config/` — use `[Required]` annotations and `[OptionsValidator]` partial validator +2. Create an operation class in `Operations/` inheriting from `Operation` base class +3. Register in `ProgramExtensions.cs` via extension method pattern +4. Add CLI options/arguments in `ParameterHelper.cs` +5. Create a JSON template in `templates/` and update `schemas/PackageUploaderOperationConfigSchema.json` +6. Document in `Operations.md` + +## Conventions + +- Operations follow command pattern: each has `DoOperation()` method +- Configuration priority: CLI args → Config file → Defaults +- Auth method selected via `-a` flag, registered in `AddIngestionAuthentication()` +- Logging: Console at Error (or Info/Trace in verbose mode), File always at Trace +- Timestamp format: `"yyyy-MM-dd HH:mm:ss.fff"` diff --git a/.github/instructions/clientapi.instructions.md b/.github/instructions/clientapi.instructions.md new file mode 100644 index 00000000..ee48c699 --- /dev/null +++ b/.github/instructions/clientapi.instructions.md @@ -0,0 +1,42 @@ +--- +applyTo: "src/PackageUploader.ClientApi/**" +--- + +# ClientApi — Partner Center Service Layer + +## Overview + +This library wraps the Partner Center Ingestion API and Xfus upload service. It is the shared backend for both the CLI and UI apps. + +## Key Interfaces + +- `IPackageUploaderService` — 14 async methods for product retrieval, package upload, config updates, removal, import, and publishing +- `IIngestionHttpClient` — REST client for Ingestion API (Client/Ingestion/) +- `IXfusUploader` — File upload handler (Client/Xfus/) + +## Upload Paths + +Two upload mechanisms exist, depending on package format: + +| Format | Upload Mechanism | Handler | +|--------|-----------------|---------| +| **XVC / UWP / MSIXVC** | Xfus upload service | `IXfusUploader` (Client/Xfus/) | +| **MSIXVC2** | `makepkg2 upload /msixvc2` | External process invocation (makepkg2.exe) | + +MSIXVC2 upload differs fundamentally from legacy: +- Uses `makepkg2 upload /msixvc2` as an external process (not the Xfus HTTP client) +- Supports loose-file upload (no prior packaging step) or packaged upload (`-UsePackage`) +- Delta uploads between versions are built into `makepkg2 upload` +- Auth forwarded via `/auth` flag (Browser, AzureCli, etc.) +- NuGet: `Microsoft.Xbox.Packaging.Tools.makepkg2` + +## Conventions + +- Register services via `AddPackageUploaderService()` and `AddIngestionAuthentication()` extension methods +- HTTP clients configured via `AddHttpClient<>()` with Polly retry policies (`DecorrelatedJitterBackoffV2`) +- Auth tokens injected via `IngestionAuthenticationDelegatingHandler` +- Paginated results use `GetAsyncEnumerable()` with `System.Linq.Async` +- All public methods are async and accept `CancellationToken` +- Throw typed exceptions: `ProductNotFoundException`, `PackageBranchNotFoundException`, `SubmissionNotFoundException` +- Log requests/responses at Trace level via `LogRequestVerboseAsync()` +- Config validation uses `[OptionsValidator]` partial classes with `[Required]` annotations diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 00000000..8599fd9e --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,30 @@ +--- +applyTo: "**/*.Test/**,**/*Test.cs,**/*Tests.cs" +--- + +# Test Conventions + +## Framework + +- MSTest (`[TestClass]`, `[TestMethod]`) +- Moq for mocking (inject via `Mock()`) + +## Patterns + +- Follow Arrange/Act/Assert pattern +- Use `[TestInitialize]` for per-test setup, `[TestCleanup]` for teardown +- Name test classes: `{ClassName}Test.cs` +- Name test methods descriptively: `Constructor_InitializesProperties()`, `Method_Condition_ExpectedBehavior()` +- Integration tests suffixed `IntegrationTest` — these touch file system or DI container +- WPF tests use `[WpfTestMethod]` instead of `[TestMethod]` (enforces STA thread) + +## Mocking + +- Mock all dependencies injected via constructor +- Use `mock.Setup()` for behavior, `mock.Verify()` for assertions +- Use `It.IsAny()` for flexible argument matching + +## Assertions + +- Use `Assert.AreEqual()`, `Assert.IsNotNull()`, `Assert.ThrowsException()` +- One logical assertion per test when possible diff --git a/.github/instructions/ui.instructions.md b/.github/instructions/ui.instructions.md new file mode 100644 index 00000000..eb753da0 --- /dev/null +++ b/.github/instructions/ui.instructions.md @@ -0,0 +1,57 @@ +--- +applyTo: "src/PackageUploader.UI/**" +--- + +# UI — Xbox Game Package Manager (WPF Desktop) + +## Overview + +WPF MVVM application for interactive package creation and upload. Uses Microsoft.Extensions.Hosting for DI. + +## Architecture + +- **View/** — Each view is a `.xaml` + `.xaml.cs` pair (e.g., `MainPageView.xaml` + `MainPageView.xaml.cs`) +- **ViewModel/** — Inherit from `BaseViewModel`; expose `ICommand` properties bound from XAML +- **Providers/** — Service providers bridging view layer to domain logic +- **Converters/** — IValueConverter implementations for XAML binding +- **Model/** — UI-specific data models (PackageModel, ErrorModel, etc.) +- **Utility/** — Helper classes (AuthenticationService, WindowsService, etc.) + +## UI Component Pattern (MVVM Command Flow) + +Each view is a XAML + code-behind pair. **Code-behind is minimal** — it only sets `DataContext` to the ViewModel and wires `Loaded` events: + +``` +View/MainPageView.xaml ← XAML layout, binds buttons via Command="{Binding SignInCommand}" +View/MainPageView.xaml.cs ← Code-behind: sets DataContext = viewModel (injected via constructor) +ViewModel/MainPageViewModel.cs ← Declares ICommand properties, executes logic via RelayCommand lambdas +``` + +**Button click flow:** +1. XAML: `