Skip to content
44 changes: 44 additions & 0 deletions .github/agents/code-reviewer.md
Original file line number Diff line number Diff line change
@@ -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<T> with `[OptionsValidator]` partial classes and `[Required]` annotations
- HTTP clients use Polly retry with `DecorrelatedJitterBackoffV2`
- All async methods accept `CancellationToken`
- Logging uses ILogger<T> 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
130 changes: 130 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -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<T> 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<T> 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 <contentDir> /pd <outputDir> [/encrypt] [/compress brotli] [/noembed] [/skipvalidation]
makepkg2 upload /msixvc2 [/auth Browser] [/branch ...]
packageutil2 extract /package <file>.msixvc /out <outputDir>
```

**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
26 changes: 26 additions & 0 deletions .github/instructions/application.instructions.md
Original file line number Diff line number Diff line change
@@ -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"`
42 changes: 42 additions & 0 deletions .github/instructions/clientapi.instructions.md
Original file line number Diff line number Diff line change
@@ -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
30 changes: 30 additions & 0 deletions .github/instructions/tests.instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
applyTo: "**/*.Test/**,**/*Test.cs,**/*Tests.cs"
---

# Test Conventions

## Framework

- MSTest (`[TestClass]`, `[TestMethod]`)
- Moq for mocking (inject via `Mock<IService>()`)

## 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<T>()` for flexible argument matching

## Assertions

- Use `Assert.AreEqual()`, `Assert.IsNotNull()`, `Assert.ThrowsException<T>()`
- One logical assertion per test when possible
57 changes: 57 additions & 0 deletions .github/instructions/ui.instructions.md
Original file line number Diff line number Diff line change
@@ -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: `<Button Command="{Binding SignInCommand}" />`
2. WPF resolves binding → finds `SignInCommand` on DataContext (the ViewModel)
3. `RelayCommand` executes the lambda defined in the ViewModel constructor
4. ViewModel updates properties → `OnPropertyChanged()` → XAML re-binds

**Never put business logic in code-behind (`.xaml.cs`) — all command logic belongs in the ViewModel.**

## Conventions

- All ViewModels inherit `BaseViewModel` (provides INotifyPropertyChanged, command infrastructure)
- Use `RelayCommand` (action) or `RelayCommand<T>` (with parameter) for ICommand — not raw delegates
- Declare commands as `public ICommand PropertyName { get; }` — initialize in constructor
- Use `Func<bool>` can-execute predicates for conditional button enablement
- Theme support: Light, Dark, High Contrast via XAML resource dictionaries in Resources/Styles/
- Auth uses `InteractiveBrowserCredentialAccessToken` with MSAL caching
- WPF tests require `[WpfTestMethod]` attribute (enforces STA thread)

## Package Format Support

The UI currently supports XVC package creation (via MakePkg.exe) and upload. MSIXVC2 support is being added:

- **MSIXVC2 creation** uses `makepkg2 pack /msixvc2` instead of legacy `MakePkg.exe`
- **MSIXVC2 upload** uses `makepkg2 upload /msixvc2` — supports both loose-file and packaged upload
- makepkg2 is distributed via NuGet: `Microsoft.Xbox.Packaging.Tools.makepkg2`
- The `Microsoft.Xbox.Packaging` library (`IPackagingOperations`) is the C# entry point for programmatic access
- Key differences from legacy: brotli compression, `/noembed` mode, loose-file upload, delta uploads, `/auth` flag forwarding
- Output: `.msixvc` files containing `.box` chunks, organized by `layout.xml`
27 changes: 27 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
## Summary

<!-- Brief description of what this PR does -->

## Related Work Items

<!-- Link to Azure DevOps work items or GitHub issues -->

## Changes

<!-- List the key changes made in this PR -->

-

## Testing

<!-- Describe how you tested these changes -->

- [ ] Unit tests pass (`dotnet test` from `./src`)
- [ ] Manual testing performed (if applicable)

## Checklist

- [ ] Code follows existing patterns and conventions
- [ ] New operations include Config class, Operation class, JSON template, and schema update
- [ ] No secrets or credentials committed
- [ ] Documentation updated (Operations.md, README.md) if applicable