Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 99 additions & 1 deletion claude.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,59 @@ PackageUpdate is a .NET global tool that updates NuGet packages for all solution
- Uses C# preview language features (`LangVersion>preview`)
- Central Package Management (CPM) is required for all target solutions

## Coding Conventions

### Lambda Expressions

Always use underscore `_` for single-parameter lambda expressions instead of named parameters:

```csharp
// ✅ Correct
packages.Where(_ => _.Id == "MyPackage")
packages.FirstOrDefault(_ => _.Version == "1.0.0")
packages.OrderByDescending(_ => _)
elements.Any(_ => _.IsEnabled)

// ❌ Incorrect - don't use named parameters
packages.Where(p => p.Id == "MyPackage")
packages.FirstOrDefault(pkg => pkg.Version == "1.0.0")
elements.Any(e => e.IsEnabled)
```

This applies even when the parameter is used multiple times in the expression:

```csharp
// ✅ Correct
xml.Descendants("PackageVersion")
.FirstOrDefault(_ =>
string.Equals(
_.Attribute("Include")?.Value,
packageName,
StringComparison.OrdinalIgnoreCase))

// ❌ Incorrect
xml.Descendants("PackageVersion")
.FirstOrDefault(e =>
string.Equals(
e.Attribute("Include")?.Value,
packageName,
StringComparison.OrdinalIgnoreCase))
```

**Exception:** Use descriptive parameter names when creating complex anonymous types or when the lambda body is long and clarity would benefit from a meaningful name:

```csharp
// Named parameter acceptable for complex Select projections
var packageVersions = xml.Descendants("PackageVersion")
.Select(element => new
{
Element = element,
Package = element.Attribute("Include")?.Value,
CurrentVersion = element.Attribute("Version")?.Value,
Pinned = element.Attribute("Pinned")?.Value == "true"
})
```

## Build and Test Commands

```bash
Expand Down Expand Up @@ -46,10 +99,11 @@ packageupdate --build

### Key Components

- **Updater.cs**: Core update logic
- **Updater.cs**: Core update and migration logic
- Parses `Directory.Packages.props` XML
- Respects `Pinned="true"` attribute to skip packages
- Queries NuGet sources for latest versions via NuGet.Protocol API
- Detects deprecated packages and auto-migrates to alternatives when current version is deprecated
- Preserves file formatting (newlines, indentation, trailing newlines)
- Only considers stable versions when current version is stable
- Only considers pre-release versions when current version is pre-release
Expand Down Expand Up @@ -93,6 +147,50 @@ The tool only works with CPM. Each solution must have a `Directory.Packages.prop

Packages with `Pinned="true"` attribute are never updated, even when explicitly targeted via `--package` flag.

### Package Migration

The tool automatically detects and migrates deprecated packages when an alternative is available.

#### How It Works

When updating packages, PackageUpdate checks if the **current version** of a package is marked as deprecated in NuGet. If the package has an alternative specified and that alternative is available in configured NuGet sources, the tool will automatically migrate:

1. Replaces the `Include` attribute with the alternative package name
2. Sets the `Version` to the latest version of the alternative (or the minimum version from the range if specified)
3. Logs the migration with deprecation reason

#### Migration Examples

```xml
<!-- Before -->
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" />

<!-- After (migrated) -->
<PackageVersion Include="Azure.Storage.Common" Version="12.26.0" />
```

#### Migration Behavior

- **Pinned packages**: Never migrated (Pinned="true" is respected)
- **No alternative available**: Package version updated normally, warning logged
- **Alternative not found**: Package version updated normally, warning logged
- **Alternative already exists**: Migration skipped, warning logged, both packages remain
- **--package flag**: Migrations still occur for specified deprecated packages
- **Current version check**: Only migrates if the **current** version is deprecated, not if only newer versions are deprecated

#### Migration Logging

Successful migration:
```
Migrated WindowsAzure.Storage -> Azure.Storage.Common (Version: 12.26.0) [Deprecated: Legacy]
```

Deprecation warnings (when no migration possible):
```
Package WindowsAzure.Storage is deprecated but has no alternative. Reasons: Legacy
Package WindowsAzure.Storage is deprecated with alternative Azure.Storage.Common, but alternative already exists
```

### Version Selection Logic

- Uses `FindPackageByIdResource` to query all versions efficiently
Expand Down
193 changes: 193 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,199 @@ The next time you run the updater, it will update to the latest version.
- Comments and formatting around pinned packages are preserved during updates


## Automatic Package Migration


### Overview

PackageUpdate automatically detects and migrates deprecated NuGet packages to their recommended alternatives. When a package is marked as deprecated on NuGet.org with an alternative package specified, the tool will automatically replace it during updates.


### How It Works

When updating packages, PackageUpdate:

1. Checks if the **current version** of each package is marked as deprecated
2. If an alternative package is specified in the deprecation metadata:
- Verifies the alternative package exists in configured NuGet sources
- Checks that the alternative doesn't already exist in `Directory.Packages.props`
- Replaces the package reference with the alternative
- Sets the version to the latest available version of the alternative
3. Logs the migration with the deprecation reason


### Example Migration

**Before:**

```xml
<Project>
<ItemGroup>
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.1" />
</ItemGroup>
</Project>
```

**After running `packageupdate`:**

```xml
<Project>
<ItemGroup>
<PackageVersion Include="Azure.Storage.Common" Version="12.26.0" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
</Project>
```

Console output:

```
Migrated WindowsAzure.Storage -> Azure.Storage.Common (Version: 12.26.0) [Deprecated: Legacy]
Updated Newtonsoft.Json: 13.0.1 -> 13.0.3
```


### Migration Behavior


#### Automatic by Default

Migrations happen automatically without requiring any flags or configuration. The tool detects deprecated packages and migrates them seamlessly.


#### Pinned Packages Are Never Migrated

If a package is pinned, it will not be migrated even if it's deprecated:

```xml
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" Pinned="true" />
```

This package will remain unchanged.


#### When Alternative Already Exists

If the alternative package already exists in `Directory.Packages.props`, the migration is skipped:

```xml
<Project>
<ItemGroup>
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" />
<PackageVersion Include="Azure.Storage.Common" Version="12.0.0" />
</ItemGroup>
</Project>
```

Output:

```
Package WindowsAzure.Storage is deprecated with alternative Azure.Storage.Common, but alternative already exists
```

Both packages remain in the file, and only `Azure.Storage.Common` gets updated to the latest version.


#### When No Alternative is Available

If a package is deprecated but has no alternative specified, the tool logs a warning and continues with normal version update:

```
Package SomeDeprecatedPackage is deprecated but has no alternative. Reasons: Legacy
```


#### Current Version Check

The tool only migrates if the **current** version you're using is deprecated. If you're on an older, non-deprecated version, and only newer versions are deprecated, no migration occurs. This prevents unnecessary migrations when you're deliberately staying on an older version.


#### Specific Package Flag

The migration feature works with the `--package` flag:

```bash
packageupdate --package WindowsAzure.Storage
```

If `WindowsAzure.Storage` is deprecated with an alternative, it will be migrated automatically.


### Common Scenarios


#### Scenario 1: Microsoft Azure SDK Packages

Many older Azure SDK packages have been deprecated in favor of the new Azure SDK:

- `WindowsAzure.Storage` → `Azure.Storage.Common` or `Azure.Storage.Blobs`
- `Microsoft.Azure.Storage.Blob` → `Azure.Storage.Blobs`
- `Microsoft.Azure.DocumentDB` → `Microsoft.Azure.Cosmos`

These migrations happen automatically when you run `packageupdate`.


#### Scenario 2: Preventing Migration

If you want to prevent migration of a deprecated package (e.g., you're not ready to migrate yet), pin the package:

```xml
<!-- Pinned: Not ready to migrate to Azure.Storage.Blobs yet -->
<PackageVersion Include="WindowsAzure.Storage" Version="9.3.3" Pinned="true" />
```


#### Scenario 3: Manual Review After Migration

After automatic migration, you may want to:

1. Review the changes in `Directory.Packages.props`
2. Update your code to use the new package's API (if breaking changes exist)
3. Test thoroughly before committing

The migration updates the package reference but doesn't modify your source code.


### Logging


#### Successful Migration

```
Migrated WindowsAzure.Storage -> Azure.Storage.Common (Version: 12.26.0) [Deprecated: Legacy]
```


#### Migration Skipped (Alternative Exists)

```
Package WindowsAzure.Storage is deprecated with alternative Azure.Storage.Common, but alternative already exists
```


#### Migration Skipped (No Alternative)

```
Package MyOldPackage is deprecated but has no alternative. Reasons: Legacy, Critical Bugs
```


#### Migration Skipped (Alternative Not Found)

```
Package OldPackage is deprecated with alternative NewPackage, but alternative not found in sources
```


### Technical Details

- Migration uses NuGet's official deprecation metadata API (`PackageDeprecationMetadata`)
- The `AlternatePackage` information comes directly from package authors via NuGet.org
- File formatting, comments, and XML structure are preserved during migration
- Migrations are logged distinctly from version updates for clarity


## Icon

[Update](https://thenounproject.com/search/?q=update&i=2060555) by [Andy Miranda](https://thenounproject.com/andylontuan88) from [The Noun Project](https://thenounproject.com/).
Loading