From 4e11e8b6b8db202ebbbb4f42a9813832b55fc0c3 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 25 Oct 2026 17:43:26 +0100 Subject: [PATCH 1/3] Legal: bump the copyright years where necessary --- .config/dotnet-tools.json.license | 2 +- CHANGELOG.md | 2 +- CONTRIBUTING.md | 2 +- TruePath.Tests/DiskUtilsTests.cs | 2 +- TruePath.Tests/GenericInterfaceTests.cs | 2 +- TruePath.Tests/PathExtensionsTests.cs | 2 +- TruePath.Tests/PathIoTests.cs | 2 +- TruePath.Tests/PathStringsTests.cs | 2 +- TruePath/LocalPathPattern.cs | 2 +- TruePath/PathExtensions.cs | 2 +- TruePath/TruePath.csproj | 2 +- docs/index.md | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.config/dotnet-tools.json.license b/.config/dotnet-tools.json.license index 8daf546..c4861d2 100644 --- a/.config/dotnet-tools.json.license +++ b/.config/dotnet-tools.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2024 Friedrich von Never +SPDX-FileCopyrightText: 2024-2026 Friedrich von Never SPDX-License-Identifier: MIT diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d51538..1c80877 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,5 @@ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ddbd46c..3a4fc3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ diff --git a/TruePath.Tests/DiskUtilsTests.cs b/TruePath.Tests/DiskUtilsTests.cs index acd735d..b7058c5 100644 --- a/TruePath.Tests/DiskUtilsTests.cs +++ b/TruePath.Tests/DiskUtilsTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 TruePath contributors +// SPDX-FileCopyrightText: 2024-2026 TruePath contributors // // SPDX-License-Identifier: MIT diff --git a/TruePath.Tests/GenericInterfaceTests.cs b/TruePath.Tests/GenericInterfaceTests.cs index d611ef5..34a7c7d 100644 --- a/TruePath.Tests/GenericInterfaceTests.cs +++ b/TruePath.Tests/GenericInterfaceTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Friedrich von Never +// SPDX-FileCopyrightText: 2024-2026 Friedrich von Never // // SPDX-License-Identifier: MIT diff --git a/TruePath.Tests/PathExtensionsTests.cs b/TruePath.Tests/PathExtensionsTests.cs index f128371..6409af9 100644 --- a/TruePath.Tests/PathExtensionsTests.cs +++ b/TruePath.Tests/PathExtensionsTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 TruePath contributors +// SPDX-FileCopyrightText: 2024-2026 TruePath contributors // // SPDX-License-Identifier: MIT diff --git a/TruePath.Tests/PathIoTests.cs b/TruePath.Tests/PathIoTests.cs index 9837266..d85debd 100644 --- a/TruePath.Tests/PathIoTests.cs +++ b/TruePath.Tests/PathIoTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2025 Friedrich von Never +// SPDX-FileCopyrightText: 2025-2026 TruePath contributors // // SPDX-License-Identifier: MIT diff --git a/TruePath.Tests/PathStringsTests.cs b/TruePath.Tests/PathStringsTests.cs index 7af6a3f..58ce94f 100644 --- a/TruePath.Tests/PathStringsTests.cs +++ b/TruePath.Tests/PathStringsTests.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 TruePath contributors +// SPDX-FileCopyrightText: 2024-2026 TruePath contributors // // SPDX-License-Identifier: MIT diff --git a/TruePath/LocalPathPattern.cs b/TruePath/LocalPathPattern.cs index bc1c194..8d28842 100644 --- a/TruePath/LocalPathPattern.cs +++ b/TruePath/LocalPathPattern.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 Friedrich von Never +// SPDX-FileCopyrightText: 2024-2026 Friedrich von Never // // SPDX-License-Identifier: MIT diff --git a/TruePath/PathExtensions.cs b/TruePath/PathExtensions.cs index 6472bc1..9ccc217 100644 --- a/TruePath/PathExtensions.cs +++ b/TruePath/PathExtensions.cs @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2024 TruePath contributors +// SPDX-FileCopyrightText: 2024-2026 TruePath contributors // // SPDX-License-Identifier: MIT diff --git a/TruePath/TruePath.csproj b/TruePath/TruePath.csproj index 4a72a10..ffd610e 100644 --- a/TruePath/TruePath.csproj +++ b/TruePath/TruePath.csproj @@ -1,5 +1,5 @@ diff --git a/docs/index.md b/docs/index.md index 1cea0ad..81da1b3 100644 --- a/docs/index.md +++ b/docs/index.md @@ -3,7 +3,7 @@ _disableBreadcrumb: true --- From 6dfdc091c6c020f1d7e11d50d24e6c4416047394 Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 25 Oct 2026 17:45:10 +0100 Subject: [PATCH 2/3] Release: prepare v2.0.0 --- CHANGELOG.md | 59 +++++++++++++++++++++++++++---------------- Directory.Build.props | 2 +- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1c80877..ed93a1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,37 +10,51 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), with one exception: the behavior of the members and types marked as `[System.Diagnostics.CodeAnalysis.ExperimentalAttribute]` may change without a major version bump (see [Versioning](README.md#versioning)). -## [Unreleased] -### Added -- `LocalPath.PathRoot`, returning the root of an absolute path, or the drive root of a drive-relative path (e.g. `C:foo`, which only exists on Windows), or `null` when the root can't be determined. -- `AbsolutePath.PathRoot` property. -- [#224](https://github.com/ForNeVeR/TruePath/issues/224): `PathKind` enumeration and `LocalPath.Kind` property, telling an absolute path from a relative one, and on Windows also from a path rooted without a drive letter (`\Windows`) and from a path relative to the current directory of a drive (`C:Windows`). These are **experimental** (diagnostic `TRUEPATH001`): more kinds may be added later, e.g. for UNC paths. -- [#202: `path.GetFiles(LocalPathPattern)`](https://github.com/ForNeVeR/TruePath/issues/202). +## [2.0.0] - 2026-10-25 +This is a major release with certain breaking changes. No public APIs have been broken, but the amount of minor behavior changes required us to bump the major version. + +The main change (outlined below) is the introduction of the _relative path kinds_ (most important for Windows). TruePath's path resolve APIs now follow the new rules regarding the relative path resolve (especially regarding more exotic relative path kinds on Windows); check the relevant documentation. + +Another notable change is that, while `LocalPath` still considers an empty path to designate the current directory, current directory now always gets normalized as `"."` and not an empty string. + +There were a bunch of unrelated important (sometimes breaking) changes to other APIs, read the details below. ### Changed -- **Breaking:** on Windows, `LocalPath.IsAbsolute` now only returns `true` for fully qualified paths, such as `C:\Windows`. Paths rooted without a drive letter (`\Windows`), and paths relative to the current directory of a drive (`C:Windows`, `C:`) are not absolute anymore, and `AbsolutePath` now rejects them. Previously, this was following the `Path.IsPathRooted` API. -- **Breaking:** `LocalPath.IsPrefixOf` and `StartsWith` now consider paths of different `PathKind` unrelated: an absolute path is never a prefix of a relative one, nor the other way round, and on Windows `\Windows`, `C:Windows` and `Windows` are all unrelated to each other. Previously, the result depended on a string comparison. -- **Breaking:** on Windows, the `/` operator of `LocalPath` and `AbsolutePath` no longer delegates to `Path.Combine`, and instead follows the algorithm of C++ `std::filesystem::path::operator/` ([fs.path.append](https://eel.is/c++draft/fs.path.append)) with case-insensitive drive letters. In particular, `C:\base / \x` is now `C:\x` (was `\x`), `C:\base / C:x` is now `C:\base\x` (was `C:x`), and `C: / x` is now `C:x` (was `C:\x`). On Unix, the behavior is unchanged. -- **Breaking:** `PathExtensions.WithExtension` now throws an `ArgumentException` unless the path ends with a file name and the result of the change is a file name in its place: an extension change may no longer alter the number or the kind of a path's segments. It used to do so silently — `new LocalPath(@"C:\foo\.gitignore").WithExtension(null)` returned `C:\foo`, `new LocalPath(".gitignore").WithExtension(null)` returned the empty path, and `new LocalPath(@"C:\").WithExtension("bar")` returned `C:\.bar`. -- On Windows, `AbsolutePath / LocalPath` now resolves a path relative to the current directory of another drive against the current directory of that drive, so the result is always absolute: e.g. `C:\base / D:x` is `D:\x` if the current directory of drive `D:` is its root. Previously, the result was `D:x`, which is not absolute. -- On Windows, `LocalPath.ResolveToCurrentDirectory` now resolves `\x` against the drive of the current directory, and `D:x` against the current directory of drive `D:`. -- **Breaking:** `LocalPath.StartsWith` and `AbsolutePath.StartsWith` now compare whole path segments instead of raw strings, which makes them exact inverses of `IsPrefixOf` as originally intended in [#43](https://github.com/ForNeVeR/TruePath/issues/43). For example, `new LocalPath("/foo1").StartsWith(new LocalPath("/foo"))` is now `false`, where it used to be `true`. +- **Breaking:** ([#224](https://github.com/ForNeVeR/TruePath/issues/224)) on Windows, `LocalPath.IsAbsolute` now only returns `true` for fully qualified paths, such as `C:\Windows`. Paths rooted without a drive letter (`\Windows`), and paths relative to the current directory of a drive (`C:Windows`, `C:`) are not absolute anymore, and `AbsolutePath` now rejects them. Previously, this was following the `Path.IsPathRooted` API. +- **Breaking:** ([#224](https://github.com/ForNeVeR/TruePath/issues/224)) on Windows, the `/` operator of `LocalPath` and `AbsolutePath` no longer delegates to `Path.Combine`, and instead follows the algorithm of C++ `std::filesystem::path::operator/` ([fs.path.append](https://eel.is/c++draft/fs.path.append)) with case-insensitive drive letters. In particular, `C:\base / \x` is now `C:\x` (was `\x`), `C:\base / C:x` is now `C:\base\x` (was `C:x`), and `C: / x` is now `C:x` (was `C:\x`). On Unix, the behavior is unchanged. - **Breaking:** path normalization now represents the current directory as `.` instead of an empty string: `PathStrings.Normalize` returns `"."` for `""`, `"."` and `"a/.."`, and so do `LocalPath.Value` and `ToString()` (and `LocalPath.Parent` of a single-segment relative path is `.`). As a consequence, `LocalPath.FileName` of such a path is `.`, the same way it is `..` for `..`, and `GetExtensionWithDot` returns `.` for it. -- `LocalPath.IsPrefixOf` now treats the current directory path `.` — the normalized form of `""`, `"."` and `"a/.."`, and the parent of any single-segment relative path — as the current directory, so it is a prefix of every path of kind `PathKind.Relative` that does not begin with a `..` reference. -- [#240](https://github.com/ForNeVeR/TruePath/issues/240): more performant `AbsolutePath.ReadKind`: it now queries the entry attributes only once, and on Windows it no longer allocates or performs additional IO in cases not concerning reparse points. Thanks to @FalconTechDev! +- **Breaking:** `LocalPath.IsPrefixOf` and `StartsWith` now consider paths of different `PathKind` unrelated: an absolute path is never a prefix of a relative one, nor the other way round, and on Windows `\Windows`, `C:Windows` and `Windows` are all unrelated to each other. +- **Breaking:** `PathExtensions.WithExtension` now throws an `ArgumentException` unless the path ends with a file name and the result of the change is a file name in its place: an extension change may no longer alter the number or the kind of any path's segments. It used to do so, e.g.: + - `new LocalPath(@"C:\foo\.gitignore").WithExtension(null)` returned `C:\foo`, + - `new LocalPath(".gitignore").WithExtension(null)` returned the empty path, + - and `new LocalPath(@"C:\").WithExtension("bar")` returned `C:\.bar`. + + None of the examples above take place anymore. +- [#224](https://github.com/ForNeVeR/TruePath/issues/224): on Windows, `AbsolutePath / LocalPath` now resolves a path relative to the current directory of another drive against the current directory of that drive, so the result is always absolute: e.g. `C:\base / D:x` is `D:\x` if the current directory of drive `D:` is its root. Previously, the result was `D:x`, which is not absolute. +- [#224](https://github.com/ForNeVeR/TruePath/issues/224): on Windows, `LocalPath.ResolveToCurrentDirectory` now resolves `\x` against the drive of the current directory, and `D:x` against the current directory of drive `D:`. +- [#240](https://github.com/ForNeVeR/TruePath/issues/240): more performant `AbsolutePath.ReadKind`: it now queries the entry attributes only once, and on Windows it no longer allocates or performs additional IO in cases not concerning reparse points. Thanks to @FalconTechDev for help! - `AbsolutePath.ReadKind` now only returns `null` if the entry doesn't exist, and throws an exception if its attributes cannot be read for another reason: e.g. an `UnauthorizedAccessException` if the access is denied, or an `IOException` if the path contains a name rejected by the file system (on .NET Framework, also `ArgumentException` or `PathTooLongException`). Previously, it returned `null` in these cases. - The platform-default path comparers (`LocalPath.PlatformDefaultComparer`, `AbsolutePath.PlatformDefaultComparer`) are now case-insensitive on iOS and tvOS as well, matching the .NET runtime. +### Added +- [#215](https://github.com/ForNeVeR/TruePath/issues/215): `AbsolutePath.PathRoot` property. Thanks to @fskalkan! +- `LocalPath.PathRoot` property, similar to the previous entry, returning the root of an absolute path, or the drive root of a drive-relative path (e.g. `C:foo`, which only exists on Windows, will return `C:`), or `null` when the root can't be determined. +- [#202: `path.GetFiles(LocalPathPattern)`](https://github.com/ForNeVeR/TruePath/issues/202). Thanks to @hamdiakin! +- [#224](https://github.com/ForNeVeR/TruePath/issues/224): `PathKind` enumeration and `LocalPath.Kind` property, telling an absolute path from a relative one, and on Windows also from a path rooted without a drive letter (`\Windows`) and from a path relative to the current directory of a drive (`C:Windows`). These are **experimental** (diagnostic `TRUEPATH001`): more kinds may be added later, e.g. for UNC paths. + ### Fixed -- `AbsolutePath.ReadKind` no longer throws an exception if the entry gets deleted during the call. - [#239](https://github.com/ForNeVeR/TruePath/issues/239): on Unix, `AbsolutePath.Canonicalize()` no longer closes the file descriptor 0 when the path doesn't exist. This used to close the standard input of the process, or any file or socket that later took this descriptor number. Also, it no longer leaks the memory allocated for the resolved path. -- [#226](https://github.com/ForNeVeR/TruePath/issues/226): `LocalPath.IsPrefixOf` and `StartsWith` now take leading `..` references of relative paths into account instead of comparing them as strings. For example, `new LocalPath("..").IsPrefixOf(new LocalPath("foo"))` is now `true`, and `new LocalPath("..").IsPrefixOf(new LocalPath("../.."))` is now `false`. -- [#225](https://github.com/ForNeVeR/TruePath/issues/225): Make path prefix checks use the same platform-default case sensitivity as path equality. -- `LocalPath.IsPrefixOf` and `StartsWith` now compare path strings ordinally. Previously they used the current culture, which ignores collation-ignorable characters, so a path could be reported as a prefix of an unrelated one. -- Path normalization no longer drops the root separator when a rooted path ends with `..`: `C:\base\..` is now normalized to `C:\` (was `C:`, a path relative to the current directory of drive `C:`), and `/base/..` to `/` (was an empty path, i.e. the current directory). This affects `PathStrings.Normalize`, `LocalPath` and `AbsolutePath`. +- [#217](https://github.com/ForNeVeR/TruePath/issues/217): the `IsPrefixOf` and `StartsWith` methods for both `LocalPath` and `AbsolutePath` are now exact opposites (use same code), and always operate on whole path segments (file and directory names) instead of raw strings. For example, `new AbsolutePath("/foo1").StartsWith(new AbsolutePath("/foo"))` is now `false`, where it used to be `true`. Thanks to @berkcantmr for help on this fix! +- [#221](https://github.com/ForNeVeR/TruePath/issues/221): `IsPrefixOf` now handles the root folders correctly as prefixes of all the absolute paths under the same root. Thanks to @berkcantmr and @t957095! +- `IsPrefixOf` and `StartsWith` now compare path strings using ordinal string comparison. +- [#225](https://github.com/ForNeVeR/TruePath/issues/225): `IsPrefixOf` and `StartsWith` now compare path strings using the same platform-default case sensitivity as path equality. Thanks to @amirani8137! +- [#224](https://github.com/ForNeVeR/TruePath/issues/224): path normalization no longer drops the root separator when a rooted path ends with `..`: `C:\base\..` is now normalized to `C:\` (was `C:`, a path relative to the current directory of drive `C:`), and `/base/..` to `/` (was an empty path, i.e. the current directory). +- [#219](https://github.com/ForNeVeR/TruePath/issues/219): path normalization no longer applies Windows drive-specific logic to all paths. Thanks to @hamdiakin! - [#95](https://github.com/ForNeVeR/TruePath/issues/95): Path normalization now drops a `..` directly after the root, since the root has no parent: `/..` is normalized to `/`, and `C:\..\x` to `C:\x`, the same as `Path.GetFullPath` does. Thanks to @sinthu-14 for help! +- [#226](https://github.com/ForNeVeR/TruePath/issues/226): `LocalPath.IsPrefixOf` and `StartsWith` now take leading `..` references of relative paths into account instead of comparing them as strings. For example, `new LocalPath("..").IsPrefixOf(new LocalPath("foo"))` is now `true`, and `new LocalPath("..").IsPrefixOf(new LocalPath("../.."))` is now `false`. Thanks to @rghvgrv! +- In .NET Standard 2.0's version of TruePath (e.g., the one targeting .NET Framework), `LocalPath.RelativeTo` and `AbsolutePath.RelativeTo` now use a port of the .NET runtime's `Path.GetRelativePath`, and return the same results as on .NET 8+ (except for the stream syntax, see [#235](https://github.com/ForNeVeR/TruePath/issues/235)). Previously, the result was wrong for a destination equal to or above the base path, and for names containing `%XX` sequences. +- `AbsolutePath.ReadKind` no longer throws an exception if the entry gets deleted during the call. - `LocalPath.RelativeTo` no longer throws an exception when either path designates the current directory. -- On .NET Standard 2.0 (e.g., .NET Framework), `LocalPath.RelativeTo` and `AbsolutePath.RelativeTo` now use a port of the .NET runtime's `Path.GetRelativePath`, and return the same results as on .NET 8+ (except for the stream syntax, see [#235](https://github.com/ForNeVeR/TruePath/issues/235)). Previously, the result was wrong for a destination equal to or above the base path, and for names containing `%XX` sequences. ## [1.12.0] - 2026-03-14 ### Changed @@ -227,4 +241,5 @@ This is the first published version of the package. It doesn't contain any featu [1.10.0]: https://github.com/ForNeVeR/TruePath/compare/v1.9.0...v1.10.0 [1.11.0]: https://github.com/ForNeVeR/TruePath/compare/v1.10.0...v1.11.0 [1.12.0]: https://github.com/ForNeVeR/TruePath/compare/v1.11.0...v1.12.0 -[Unreleased]: https://github.com/ForNeVeR/TruePath/compare/v1.12.0...HEAD +[2.0.0]: https://github.com/ForNeVeR/TruePath/compare/v1.12.0...v2.0.0 +[Unreleased]: https://github.com/ForNeVeR/TruePath/compare/v2.0.0...HEAD diff --git a/Directory.Build.props b/Directory.Build.props index 98222c1..4079d9c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -6,7 +6,7 @@ SPDX-License-Identifier: MIT - 1.12.0 + 2.0.0 MIT 2024-2026 TruePath contributors <https://github.com/ForNeVeR/TruePath>; .NET Foundation and Contributors README.md From f49bfbc352f841c602e083091d7cbacfddeda2de Mon Sep 17 00:00:00 2001 From: Friedrich von Never Date: Sun, 25 Oct 2026 17:51:23 +0100 Subject: [PATCH 3/3] CI: migrate to Trusted Publishing Policy --- .github/workflows/release.yml | 8 +++++- MAINTAINING.md | 47 ++++++++++++++++++----------------- scripts/github-actions.fsx | 18 +++++++++++--- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7202e81..ba39ac7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,9 +65,15 @@ jobs: ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.${{ steps.version.outputs.version }}.nupkg ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.${{ steps.version.outputs.version }}.snupkg name: TruePath v${{ steps.version.outputs.version }} + - if: startsWith(github.ref, 'refs/tags/v') + id: nuget-login + name: Log in to NuGet + uses: NuGet/login@v1 + with: + user: ${{ secrets.NUGET_USER }} - if: startsWith(github.ref, 'refs/tags/v') name: Push TruePath to NuGet run: dotnet nuget push ./TruePath/bin/Release/TruePath.${{ steps.version.outputs.version }}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_TOKEN_TRUE_PATH }} - if: startsWith(github.ref, 'refs/tags/v') name: Push TruePath.SystemIo to NuGet - run: dotnet nuget push ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.${{ steps.version.outputs.version }}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_TOKEN_TRUE_PATH_SYSTEM_IO }} + run: dotnet nuget push ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.${{ steps.version.outputs.version }}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ steps.nuget-login.outputs.NUGET_API_KEY }} diff --git a/MAINTAINING.md b/MAINTAINING.md index 8a2beaa..e8ee483 100644 --- a/MAINTAINING.md +++ b/MAINTAINING.md @@ -1,5 +1,5 @@ @@ -15,27 +15,28 @@ Publish a New Version 4. Prepare a corresponding entry in the `CHANGELOG.md` file (usually by renaming the "Unreleased" section). 5. Set `` in the `Directory.Build.props` file. 6. Merge the aforementioned changes via a pull request. -7. Check if the NuGet keys are still valid (see the **Rotate NuGet Publishing Keys** section if they aren't). +7. If you want to publish a new package (not a new version, but a new project/package) change the NuGet Trusted Publishing Policy: switch from **push only new packages versions** to **push new packages and package versions** (see below for more details). 8. Push a tag in form of `v`, e.g. `v0.0.0`. GitHub Actions will do the rest (push a NuGet packages). - -Rotate NuGet Publishing Keys ----------------------------- -CI relies on NuGet API keys being added to the secrets. -From time to time, these keys require maintenance: they will become obsolete and will have to be updated. - -To update the key: - -1. Sign in onto nuget.org. -2. Go to the [API keys][nuget.api-keys] section. -3. Update the existing or create a new key named `truepath.github` with a permission to **Push only new package versions** and only allowed to publish the package **TruePath**. - - (If this is the first publication of a new package, - upload a temporary short-living key with permission to add new packages and rotate it afterward.) -4. Update the existing or create a new key named `truepath.systemio.github` with a permission to **Push only new package versions** and only allowed to publish the package **TruePath.SystemIo**. - - (If this is the first publication of a new package, - upload a temporary short-living key with permission to add new packages and rotate it afterward.) -5. Paste the generated keys, correspondingly, to the `NUGET_TOKEN_TRUE_PATH` and `NUGET_TOKEN_TRUE_PATH_SYSTEM_IO` variables on the [action secrets][github.secrets] section of GitHub settings. - +9. If you were publishing a new package, revert back the change you did at step 8. + +NuGet Publishing Policy +----------------------- +This repository relies on [NuGet Trusted Publishing][docs.nuget-trusted-publishing] policy. In case you need to create it again, follow these steps: + +1. Sign in to nuget.org. +2. Go to the [Trusted Publishing][nuget.trusted-publishing] section. +3. Create a new policy: + - **Policy Name:** `github.truepath`. + - **Repository Owner:** `ForNeVeR`. + - **Repository**: `TruePath`. + - **Workflow File**: `release.yml`. + - **Environment:** _leave empty_. + - **Scope:** **push only new packages versions**. + - **Glob Patterns and Packages**: `TruePath*`. +4. Put your nuget.org username into the `NUGET_USER` variable on the [action secrets][github.secrets] section of GitHub settings. + +When introducing a new package, you can adjust the policy temporarily to allow it to **push new packages and package versions**. + +[docs.nuget-trusted-publishing]: https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing [github.secrets]: https://github.com/ForNeVeR/TruePath/settings/secrets/actions -[nuget.api-keys]: https://www.nuget.org/account/apikeys +[nuget.trusted-publishing]: https://www.nuget.org/account/trustedpublishing diff --git a/scripts/github-actions.fsx b/scripts/github-actions.fsx index 953dbc7..07b38fd 100644 --- a/scripts/github-actions.fsx +++ b/scripts/github-actions.fsx @@ -164,8 +164,9 @@ let workflows = [ ] ) + let whenPushingTag = "startsWith(github.ref, 'refs/tags/v')" step( - condition = "startsWith(github.ref, 'refs/tags/v')", + condition = whenPushingTag, name = "Create a release", usesSpec = Auto "softprops/action-gh-release", options = Map.ofList [ @@ -175,14 +176,23 @@ let workflows = [ ] ) step( - condition = "startsWith(github.ref, 'refs/tags/v')", + condition = whenPushingTag, + id = "nuget-login", + name = "Log in to NuGet", + usesSpec = Auto "NuGet/login", + options = Map.ofList [ + "user", "${{ secrets.NUGET_USER }}" + ] + ) + step( + condition = whenPushingTag, name = "Push TruePath to NuGet", run = $"dotnet nuget push ./TruePath/bin/Release/TruePath.{versionField}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{{{ secrets.NUGET_TOKEN_TRUE_PATH }}}}" ) step( - condition = "startsWith(github.ref, 'refs/tags/v')", + condition = whenPushingTag, name = "Push TruePath.SystemIo to NuGet", - run = $"dotnet nuget push ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.{versionField}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{{{ secrets.NUGET_TOKEN_TRUE_PATH_SYSTEM_IO }}}}" + run = $"dotnet nuget push ./TruePath.SystemIo/bin/Release/TruePath.SystemIo.{versionField}.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{{{ steps.nuget-login.outputs.NUGET_API_KEY }}}}" ) ] ]