diff --git a/.changeset/config.json b/.changeset/config.json index 7bd3e3e..7d0c2ec 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -1,4 +1,4 @@ { "sourcePath": "src", "packageSource": "nuget" -} \ No newline at end of file +} diff --git a/.editorconfig b/.editorconfig index 8f9c51e..042acdd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,6 +11,10 @@ indent_size = 4 trim_trailing_whitespace = true # end_of_line = crlf # The end_of_line setting is maintained by .gitattributes! Do not set it here! +[*.{json,yml,yaml,xml,md,csproj,props,config}] +indent_style = space +indent_size = 2 + [*.cs] charset = utf-8 max_line_length = 130 diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 1fcce10..a32d47c 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -12,10 +12,10 @@ Add the issue number here. e.g. #123 # Checklist -- [ ] I have double checked my own changes +- [ ] I have double-checked my own changes - [ ] I have read the [Contribution Guidelines](https://github.com/solarwinds/net-changesets/blob/main/CONTRIBUTING.md) -- [ ] Include the issue number (#xxx) in branch name and PR title and description -- [ ] Provide a reasonable description of the PR in the [Changes](#Changes) section +- [ ] Include the issue number (#xxx) in the branch name, PR title, and PR description in the Issue number section above +- [ ] Provide a reasonable description of the PR in the [Changes](#changes) section - [ ] I have commented on the issue above and discussed the intended changes - [ ] All newly added code is adequately covered by tests - [ ] Make sure your PR is passing the CI/CD pipeline diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bc17de9..3714b68 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -8,14 +8,14 @@ NET Changesets is a .NET CLI tool for managing versioning and changelogs in mult ## Technology stack -**Language:** C# 12.0 with nullable reference types and implicit usings enabled -**Runtime:** .NET 8.0 (SDK 8.0.406) -**CLI Framework:** Spectre.Console v0.50.0 (interactive prompts, tables, markup rendering) -**Dependency Injection:** Microsoft.Extensions.DependencyInjection v9.0.9 -**Testing:** NUnit 4.4.0, Moq 4.20.72, AwesomeAssertions 9.1.0, Spectre.Console.Testing -**Code Analysis:** Microsoft .NET analyzers (all enabled, warnings-as-errors), Spectre.Console.Analyzer -**Package Management:** Central Package Management via `Directory.Packages.props` -**CI/CD:** GitHub Actions (build, test, pack, format verification) +**Language:** C# 12.0 with nullable reference types and implicit usings enabled +**Runtime:** .NET 8.0 (SDK 8.0.406) +**CLI Framework:** Spectre.Console v0.50.0 (interactive prompts, tables, markup rendering) +**Dependency Injection:** Microsoft.Extensions.DependencyInjection v9.0.9 +**Testing:** NUnit 4.4.0, Moq 4.20.72, AwesomeAssertions 9.1.0, Spectre.Console.Testing +**Code Analysis:** Microsoft .NET analyzers (all enabled, warnings-as-errors), Spectre.Console.Analyzer +**Package Management:** Central Package Management via `Directory.Packages.props` +**CI/CD:** GitHub Actions (build, test, pack, format verification) **External Tools:** Git CLI (for diff detection), dotnet CLI (for pack/publish) ## Directory structure @@ -44,6 +44,7 @@ SolarWinds.Changesets.sln # Main solution file **CLI Framework:** Uses Spectre.Console.Cli with `CommandApp` (Add is default). Commands registered in `Program.cs` via `config.AddCommand()`. All commands inherit from `ConfigurationCommandBase` which loads `.changeset/config.json`. **Dependency Injection:** Services registered in `ServiceCollection` and integrated via custom `TypeRegistrar`/`TypeResolver` (required by Spectre.Console.Cli). Main services: + - `IConfigurationService`: Loads/validates `.changeset/config.json` - `IChangesetsRepository`: Reads/writes/deletes changeset markdown files - `ICsProjectsRepository`: Parses .csproj files, updates `` elements @@ -55,6 +56,7 @@ SolarWinds.Changesets.sln # Main solution file **Semantic Versioning:** Custom `Semver` class (Major.Minor.Patch) with methods `RaiseMajor()`, `RaiseMinor()`, `RaisePatch()`. Parses version strings from `` in .csproj files. Version bumps cascade dependencies (updating dependent projects). **Command Flow Example (version command):** + 1. `VersionChangesetCommand.ExecuteCommandAsync()` calls `IChangesetsRepository.GetChangesetsAsync()` 2. Reads all `.md` files from `.changeset/` directory 3. Parses YAML front matter (project names, bump types) and markdown content @@ -69,28 +71,33 @@ SolarWinds.Changesets.sln # Main solution file **Prerequisites:** .NET 8.0 SDK (version 8.0.406 or compatible via rollForward in `global.json`) **Build:** + ```bash dotnet restore --packages ./packages dotnet build -c Release --no-restore ``` **Test:** + ```bash dotnet test -c Release --no-restore --no-build --verbosity normal ``` **Pack:** + ```bash dotnet pack -c Release --no-restore --no-build # Output: ./nupkg/SolarWinds.Changesets.0.1.1.nupkg ``` **Code Style Check:** + ```bash dotnet format --no-restore --verify-no-changes ``` **Local Installation for Testing:** + ```bash dotnet tool uninstall solarwinds.changesets --global dotnet tool install solarwinds.changesets --global --add-source ./nupkg @@ -109,6 +116,7 @@ dotnet run --project .\src\SolarWinds.Changesets\SolarWinds.Changesets.csproj ``` **Verification checklist:** + 1. ✅ Build succeeds: `dotnet build -c Release --no-restore` 2. ✅ All tests pass: `dotnet test -c Release --no-restore --no-build` 3. ✅ Code formatting: `dotnet format --no-restore --verify-no-changes` @@ -118,15 +126,18 @@ dotnet run --project .\src\SolarWinds.Changesets\SolarWinds.Changesets.csproj ## Domain configurations **Changeset Config (`.changeset/config.json`):** + ```json { - "sourcePath": "src", // Relative path to projects folder - "packageSource": "nuget" // NuGet source name from NuGet.config + "sourcePath": "src", // Relative path to projects folder + "packageSource": "nuget" // NuGet source name from NuGet.config } ``` + Created by `changeset init`. Loaded by `ConfigurationService`. Default `packageSource` is `nuget.org`. **NuGet.config:** Optional file for custom package sources. Example: + ```xml @@ -135,6 +146,7 @@ Created by `changeset init`. Loaded by `ConfigurationService`. Default `packageS ``` **Changeset File Format (`.changeset/{randomname}.md`):** + ```markdown --- "ProjectA": minor @@ -143,9 +155,11 @@ Created by `changeset init`. Loaded by `ConfigurationService`. Default `packageS Added new feature X and fixed bug Y in ProjectB ``` + Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Projects listed in YAML front matter with bump type (major/minor/patch). **Project File Requirements:** + - Must contain `` element (e.g., `1.0.0`) - Supported format: `Major.Minor.Patch` (parsed via `System.Version`) - `version` command updates this element in-place using XML manipulation @@ -153,6 +167,7 @@ Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Proje ## Conventions **Code Style:** + - **Implicit usings enabled** (no need for common System namespaces) - **Nullable reference types enabled** (treat null warnings as errors) - **File-scoped namespaces** (e.g., `namespace SolarWinds.Changesets.Commands.Add;`) @@ -161,6 +176,7 @@ Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Proje - **IDE rules enforced in CI** via `dotnet format` (not in build) **Testing:** + - NUnit framework with `[TestFixture]` and `[Test]` attributes - Global using for `NUnit.Framework` (defined in test .csproj) - Test data in `TestData/` folders, copied to output directory @@ -168,23 +184,27 @@ Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Proje - Mock external processes using `Moq` on `IProcessExecutor` **Naming:** + - Commands: `{Action}ChangesetCommand` (e.g., `AddChangesetCommand`) - Interfaces: Standard `I` prefix (e.g., `IConfigurationService`) - Internal classes: Most implementation classes are `internal sealed` - Constants: Defined in `Constants` static class (e.g., `Constants.WorkingDirectoryFullPath`) **Dependency Constraints:** + - **Only allowed third-party dependency:** Spectre.Console (and related packages) - Rationale: Minimize external dependencies for a CLI tool - All other needs met by .NET BCL or Microsoft.Extensions packages **Git Workflow:** + - Branch naming: `{type}/{issueID}-{description}` (e.g., `feature/123-add-new-command`) - Commit messages: Start with issue ID (e.g., `123 Implement status command`) - GPG signing required for commits - PR title format: `{Type} #{issueID} {Description}` **Error Handling:** + - `ExceptionHandler.Handle()` registered in Spectre.Console CLI config - Custom exception: `InitializationException` for config validation errors - Return codes defined in `ResultCodes` class (not yet fully implemented) @@ -192,23 +212,27 @@ Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Proje ## Integration points **Git Integration:** + - `GitService.GetDiff()` calls `git diff --name-only {sourcePath}` to detect modified .csproj files - Used by `publish` command to identify packages needing publication - Requires git executable in PATH **Dotnet CLI Integration:** + - `DotnetService.Pack()` calls `dotnet pack {projectPath} --output {Constants.NupkgOutputFullPath}` - `DotnetService.Publish()` calls `dotnet nuget push {nupkgPath} --source {packageSource}` - NuGet API key expected in environment or nuget.config (standard dotnet behavior) - Output directory: `./nupkg/` (created if not exists) **File System Operations:** + - Changeset files: `.changeset/` directory (created by `init` command) - CHANGELOG.md: Root of repository - .csproj files: Located via recursive search from `sourcePath` config - All paths resolved relative to `Constants.WorkingDirectoryFullPath` (current directory) **NuGet Sources:** + - Resolved via standard dotnet/NuGet.config mechanisms - Default: `nuget` source (typically nuget.org) - Custom sources defined in `NuGet.config` (repository or user-level) @@ -219,11 +243,13 @@ Generated by `add` command. Filename: 10 random lowercase letters + `.md`. Proje **Current State:** MVP stage with manual CLI operations. All 5 commands implemented (`init`, `add`, `version`, `publish`, `status`). **Known Limitations:** + - `add` command supports only single bump type for all selected projects (npm version allows per-project bumps) - No command-line options for `add` (interactive mode only) - Manual execution required (no GitHub Action yet) **Planned Features:** + - **GitHub Action** (primary roadmap item): Automated PR creation for version bumps, reusing existing codebase. See `.NET GitHub Action` documentation. - Future improvements open for discussion via GitHub issues diff --git a/.github/instructions/coding.instructions.md b/.github/instructions/coding.instructions.md index 9faf408..32ecb3f 100644 --- a/.github/instructions/coding.instructions.md +++ b/.github/instructions/coding.instructions.md @@ -5,6 +5,7 @@ applyTo: "**/*.go,**/*.cs,**/*.java,**/*.kt,**/*.py,**/*.js,**/*.ts,**/*.tsx" # Coding Instructions ## General code style and readability + - Write code that is readable, understandable, and maintainable for future readers. - Aim to create software that is not only functional but also readable, maintainable, and efficient throughout its lifecycle. - Prioritize clarity to make reading, understanding, and modifying code easier. @@ -12,26 +13,31 @@ applyTo: "**/*.go,**/*.cs,**/*.java,**/*.kt,**/*.py,**/*.js,**/*.ts,**/*.tsx" - Regularly review and refactor code to improve structure, readability, and maintainability. Always leave the codebase cleaner than you found it. ## Naming conventions + - Choose names for variables, functions, and classes that reflect their purpose and behavior. - A name should tell you why it exists, what it does, and how it is used. If a name requires a comment, then the name does not reveal its intent. - Use specific names that provide a clearer understanding of what the variables represent and how they are used. ## DRY principle + - Follow the DRY (Don't Repeat Yourself) Principle and Avoid Duplicating Code or Logic. - Avoid writing the same code more than once. Instead, reuse your code using functions, classes, modules, libraries, or other abstractions. - Modify code in one place if you need to change or update it. ## Function length and responsibility + - Write short functions that only do one thing. - Follow the single responsibility principle (SRP), which means that a function should have one purpose and perform it effectively. - If a function becomes too long or complex, consider breaking it into smaller, more manageable functions. ## Comments usage + - Use comments sparingly, and when you do, make them meaningful. - Don't comment on obvious things. Excessive or unclear comments can clutter the codebase and become outdated. - Use comments to convey the "why" behind specific actions or explain unusual behavior and potential pitfalls. - Provide meaningful information about the function's behavior and explain unusual behavior and potential pitfalls. ## Conditional encapsulation + - One way to improve the readability and clarity of functions is to encapsulate nested if/else statements into other functions. - Encapsulating such logic into a function with a descriptive name clarifies its purpose and simplifies code comprehension. diff --git a/.github/instructions/dotnet.instructions.md b/.github/instructions/dotnet.instructions.md index ff1ee75..8cbfe32 100644 --- a/.github/instructions/dotnet.instructions.md +++ b/.github/instructions/dotnet.instructions.md @@ -5,6 +5,7 @@ applyTo: "**/*.cs" # Instructions for .NET C# code C# code (besides unit tests) should adhere to the following guidelines: + - Follow SOLID principles - Use dependency injection - Use appropriate configuration formats (JSON, YAML, environment variables) @@ -119,7 +120,7 @@ public async Task ProcessAsync(UserData userData, CancellationTok ## Repository structure -The following are instructions on how to name folders and files and how the basic repository structure should look like +The following are instructions on how to name folders and files and how the basic repository structure should look like - Each project (CSPROJ) should be in its own folder. - Projects with the implementation code should be under "src" folder. @@ -130,19 +131,21 @@ The following are instructions on how to name folders and files and how the basi ### Code Examples #### Async/Await Pattern + ```csharp public async Task GetUserAsync(int userId, CancellationToken cancellationToken = default) { using var httpClient = _httpClientFactory.CreateClient(); var response = await httpClient.GetAsync($"/api/users/{userId}", cancellationToken); response.EnsureSuccessStatusCode(); - + var content = await response.Content.ReadAsStringAsync(cancellationToken); return JsonSerializer.Deserialize(content); } ``` #### String Comparison Example + ```csharp // For culture-sensitive comparisons if (userInput.Equals(expectedValue, StringComparison.InvariantCultureIgnoreCase)) @@ -152,6 +155,7 @@ if (fileName.EndsWith(".txt", StringComparison.Ordinal)) ``` #### StringBuilder Usage + ```csharp // Use StringBuilder for heavy concatenation in loops var builder = new StringBuilder(); @@ -163,6 +167,7 @@ return builder.ToString(); ``` #### Regex Source Generators + ```csharp using System.Text.RegularExpressions; @@ -171,11 +176,11 @@ public partial class MyService // Use regex source generators for compile-time validation and better performance [GeneratedRegex(@"\b(query|mutation)\b\s+(\w+)")] private static partial Regex OperationNameRegex(); - + // For culture-sensitive patterns, specify options [GeneratedRegex(@"[a-zA-Z_:][a-zA-Z0-9_:]*", RegexOptions.IgnoreCase)] private static partial Regex IdentifierRegex(); - + public string ExtractOperationName(string query) { var match = OperationNameRegex().Match(query); diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index abaca15..f724d3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,41 +3,41 @@ permissions: contents: read on: - pull_request: - branches: [ "main" ] - push: - branches: [ "main" ] + pull_request: + branches: ["main"] + push: + branches: ["main"] jobs: build-and-test: runs-on: ubuntu-latest steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.406 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.406 - - name: Restore dependencies - run: dotnet restore --packages ./packages + - name: Restore dependencies + run: dotnet restore --packages ./packages - - name: Build - run: dotnet build -c Release --no-restore + - name: Build + run: dotnet build -c Release --no-restore - - name: Test - run: dotnet test -c Release --no-restore --no-build --verbosity normal + - name: Test + run: dotnet test -c Release --no-restore --no-build --verbosity normal - - name: Pack - run: dotnet pack -c Release --no-restore --no-build + - name: Pack + run: dotnet pack -c Release --no-restore --no-build - - name: Upload NuGet package as artifact - uses: actions/upload-artifact@v4 - with: - name: nuget-package - path: ./nupkg/*.nupkg + - name: Upload NuGet package as artifact + uses: actions/upload-artifact@v4 + with: + name: nuget-package + path: ./nupkg/*.nupkg - - name: Format - run: dotnet format --no-restore --verify-no-changes + - name: Format + run: dotnet format --no-restore --verify-no-changes diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index cd1bfcf..9b059ba 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -2,11 +2,11 @@ name: "CodeQL Advanced" on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] schedule: - - cron: '0 0 * * 1' + - cron: "0 0 * * 1" jobs: analyze: @@ -22,30 +22,30 @@ jobs: fail-fast: false matrix: include: - - language: actions - build-mode: none - - language: csharp - build-mode: none + - language: actions + build-mode: none + - language: csharp + build-mode: none steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: 8.0.406 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.406 - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - build-mode: ${{ matrix.build-mode }} - queries: security-and-quality + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + queries: security-and-quality - - name: Autobuild - uses: github/codeql-action/autobuild@v3 - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..eb89390 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "editorconfig.editorconfig", + "ms-dotnettools.csharp", + "redhat.vscode-xml", + "yzhang.markdown-all-in-one" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..ecdfead --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,36 @@ +{ + // Ensure EditorConfig settings are respected + "editor.detectIndentation": false, + // Format on save + "editor.formatOnSave": true, + // Use EditorConfig for indentation and final newline + "editor.insertSpaces": true, + "editor.tabSize": 4, + "files.insertFinalNewline": true, + "files.trimTrailingWhitespace": true, + // File-specific formatter settings + "[json]": { + "editor.defaultFormatter": "vscode.json-language-features", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + "[jsonc]": { + "editor.defaultFormatter": "vscode.json-language-features", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + "[xml]": { + "editor.defaultFormatter": "redhat.vscode-xml", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + "[markdown]": { + "editor.defaultFormatter": "yzhang.markdown-all-in-one", + "editor.tabSize": 2, + "editor.insertSpaces": true + }, + // C# formatting - respects EditorConfig automatically + "[csharp]": { + "editor.defaultFormatter": "ms-dotnettools.csharp" + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4ae0db7..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,19 +0,0 @@ -# Changelog - -## 0.1.1 - -### Modified - -- Package icon, description, url,... - -## 0.1.0 - -### Added - -- Initial release of NET Changesets tool -- Currently Supported Commands: - - `changeset init` - - `changeset add` - - `changeset version` - - `changeset publish` - - `changeset status` diff --git a/CODEOWNERS b/CODEOWNERS index 83cf8cf..606a970 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -1 +1 @@ -* @solarwinds/eng-changeset-owners \ No newline at end of file +* @solarwinds/eng-pub-changeset-owners diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8305a62..d995597 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ We do very welcome everyone who wants to contribute to this repository, provided Please **discuss any changes you wish to make** with the net-changesets maintainers via GitHub issues before proceeding. -If you don’t have a specific idea in mind, you can pick an issue from the [roadmap](ROADMAP.md). Your help is greatly appreciated! +If you don’t have a specific idea in mind, you can pick an issue from the [roadmap](https://github.com/solarwinds/net-changesets/blob/main/ROADMAP.md). Your help is greatly appreciated! ## Design considerations @@ -31,7 +31,7 @@ Always consider the following aspects: - **Do not make any additional changes unrelated to your GitHub issue.** - **Every change must be covered by a unit or integration test.** - **Write clean, self-documenting code** and add XML comments to newly added code. -- **Edit or add documentation** in the `docs` folder or in [README.md](README.md). +- **Edit or add documentation** in the `docs` folder or in [README.md](https://github.com/solarwinds/net-changesets/blob/main/README.md). #### Local Installation @@ -49,7 +49,7 @@ dotnet tool install solarwinds.changesets --global --add-source ./nupkg --versio This project uses only first-party [.NET source code analysis](https://learn.microsoft.com/en-us/dotnet/fundamentals/code-analysis/overview?tabs=net-9) from Microsoft. -The [.editorconfig](.editorconfig) and [Directory.Build.props](Directory.Build.props) files contain the configuration for code style and quality. +The [.editorconfig](https://github.com/solarwinds/net-changesets/blob/main/.editorconfig) and [Directory.Build.props](https://github.com/solarwinds/net-changesets/blob/main/Directory.Build.props) files contain the configuration for code style and quality. - **Code style (`IDE****`) analyzers are disabled during build time.** - They are checked in PR CI as the last step. @@ -85,4 +85,4 @@ Create a pull request only if: - **PR author will reply** (for example, confirm that requested changes have been implemented or discuss why a suggested change may not be appropriate). - **Reviewer will resolve comments** once satisfied with the changes or explanations. -Additional information can be found in [PULL_REQUEST_TEMPLATE](.github/PULL_REQUEST_TEMPLATE.md). +Additional information can be found in [PULL_REQUEST_TEMPLATE](https://github.com/solarwinds/net-changesets/blob/main/.github/PULL_REQUEST_TEMPLATE.md). diff --git a/Directory.Build.props b/Directory.Build.props index 0393668..3cea168 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -36,6 +36,107 @@ $(NoWarn); + + + + + $(WarningsNotAsErrors); + IDE0003;IDE0005;IDE0007;IDE0008;IDE0011;IDE0016;IDE0017;IDE0018;IDE0019; + IDE0020;IDE0021;IDE0022;IDE0023;IDE0024;IDE0025;IDE0026;IDE0027;IDE0028; + IDE0029;IDE0030;IDE0031;IDE0032;IDE0033;IDE0034;IDE0036;IDE0037;IDE0038; + IDE0039;IDE0040;IDE0041;IDE0042;IDE0044;IDE0045;IDE0046;IDE0049;IDE0053; + IDE0054;IDE0055;IDE0056;IDE0057;IDE0058;IDE0059;IDE0060;IDE0061;IDE0062; + IDE0063;IDE0065;IDE0066;IDE0070;IDE0071;IDE0074;IDE0075;IDE0078;IDE0079; + IDE0083;IDE0090;IDE0130;IDE0150;IDE0161;IDE0170;IDE0180;IDE0200;IDE0210; + IDE0220;IDE0230;IDE0250;IDE0251;IDE0270;IDE0290;IDE0320;IDE0330;IDE0340; + IDE1005;IDE1006;IDE2000;IDE2001;IDE2002;IDE2003;IDE2004;IDE2005;IDE2006; + IDE2007;CS0612;CS0618 + diff --git a/README.md b/README.md index 5feeaa1..49573e9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # NET Changesets +[![NuGet version (SolarWinds.Changesets)](https://img.shields.io/nuget/v/SolarWinds.Changesets.svg)](https://www.nuget.org/packages/SolarWinds.Changesets) + A .NET CLI tool (with interactive support) to manage versioning and changelogs with a focus on multi-package repositories. Key features include: @@ -13,26 +15,37 @@ Key features include: This .NET implementation is port from original `npm` implementation [@changesets/cli](https://www.npmjs.com/package/@changesets/cli) ([GitHub repository](https://github.com/changesets/changesets/blob/main/README.md)). -## Currently Supported Commands +## CLI + +**Usage:** + +`changeset [OPTIONS] [COMMAND]` + +**Options:** + +- `-h, --help` Prints help information +- `-v, --version` Prints version information + +**Commands:** -- `changeset init` -- `changeset add` -- `changeset version` -- `changeset publish` -- `changeset status` +- `init` Sets up the .changeset folder and generates a default config file. You should run this command once when you are setting up changesets +- `add` Used by contributors to add information about their changes by creating changeset files +- `version` Takes existing changeset files and updates versions and dependencies of packages, as well as writing changelogs +- `publish` This publishes changes to specified nuget repository +- `status` Provides information about the changesets that currently exist. If there are no changesets present, it exits with an error status code ## Documentation -- [Implementation Details of net-changesets commands](./docs/commands-implementation-details.md) -- [Config file options](./docs/config-file-options.md) +- [Implementation Details of net-changesets commands](https://github.com/solarwinds/net-changesets/blob/main/docs/commands-implementation-details.md) +- [Config file options](https://github.com/solarwinds/net-changesets/blob/main/docs/config-file-options.md) ## Roadmap -You can find the **Roadmap** [here](./ROADMAP.md). +You can find the **Roadmap** [here](https://github.com/solarwinds/net-changesets/blob/main/ROADMAP.md). ## Contributing -You can find the **Contribution Guidelines** [here](./CONTRIBUTING.md). +You can find the **Contribution Guidelines** [here](https://github.com/solarwinds/net-changesets/blob/main/CONTRIBUTING.md). ## Installation diff --git a/SECURITY.md b/SECURITY.md index 1482a4f..562a3b1 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,9 +1,11 @@ # Security + SolarWinds is committed to taking our customers security and privacy concerns seriously and makes it a priority. -We strive to implement and maintain security processes, procedures, standards, and take all reasonable care to +We strive to implement and maintain security processes, procedures, standards, and take all reasonable care to prevent unauthorized access to our customer data. For more information see https://www.solarwinds.com/information-security. ## Want to report a security concern? + To report a vulnerability in one of our products or solutions or a vulnerability in one of our corporate websites, please contact our Product Security Incident Response Team (PSIRT) at psirt@solarwinds.com. diff --git a/SolarWinds.Changesets.sln b/SolarWinds.Changesets.sln index b146166..4318e29 100644 --- a/SolarWinds.Changesets.sln +++ b/SolarWinds.Changesets.sln @@ -15,7 +15,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig .gitattributes = .gitattributes .gitignore = .gitignore - CHANGELOG.md = CHANGELOG.md CODEOWNERS = CODEOWNERS CONTRIBUTING.md = CONTRIBUTING.md Directory.Build.props = Directory.Build.props @@ -27,6 +26,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{14F3844A-0818-4A24-B210-262CFFC5FD8C}" ProjectSection(SolutionItems) = preProject + .github\copilot-instructions.md = .github\copilot-instructions.md .github\PULL_REQUEST_TEMPLATE.md = .github\PULL_REQUEST_TEMPLATE.md EndProjectSection EndProject @@ -51,6 +51,17 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{ .github\workflows\codeql.yml = .github\workflows\codeql.yml EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".changeset", ".changeset", "{D9292B07-1521-45FF-987C-93AF914C02B8}" + ProjectSection(SolutionItems) = preProject + .changeset\config.json = .changeset\config.json + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "instructions", "instructions", "{32ECDA7C-AFB5-4446-97BC-47BE1AB7AD78}" + ProjectSection(SolutionItems) = preProject + .github\instructions\coding.instructions.md = .github\instructions\coding.instructions.md + .github\instructions\dotnet.instructions.md = .github\instructions\dotnet.instructions.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -76,6 +87,8 @@ Global {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} = {0ABD51BE-854F-4ADA-BA0B-A53D22E84A2B} {31B2BA1F-7D13-210D-5051-E57059416DA2} = {BC9915A0-AE80-47A7-89E8-841B2220E53B} {00407218-C694-4CF5-AABB-5EAE81B3466F} = {14F3844A-0818-4A24-B210-262CFFC5FD8C} + {D9292B07-1521-45FF-987C-93AF914C02B8} = {0ABD51BE-854F-4ADA-BA0B-A53D22E84A2B} + {32ECDA7C-AFB5-4446-97BC-47BE1AB7AD78} = {14F3844A-0818-4A24-B210-262CFFC5FD8C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {24D73EE6-7A9E-4AFF-AC21-18B414246B30} diff --git a/assets/icon.png b/assets/icon.png index 864dabf..16cd68d 100644 Binary files a/assets/icon.png and b/assets/icon.png differ diff --git a/docs/commands-implementation-details.md b/docs/commands-implementation-details.md index f18a70b..2f8da1f 100644 --- a/docs/commands-implementation-details.md +++ b/docs/commands-implementation-details.md @@ -8,7 +8,7 @@ On this page, we will discuss implementation details that differ slightly from t changeset init ``` -This command works the same, only difference is that we have different configuration schema to satisfy .NET implementation. You can read more about it in [config-file-options.md](./config-file-options.md). +This command works the same, only difference is that we have different configuration schema to satisfy .NET implementation. You can read more about it in [config-file-options.md](https://github.com/solarwinds/net-changesets/blob/main/docs/config-file-options.md). ## `add` diff --git a/src/SolarWinds.Changesets/CHANGELOG.md b/src/SolarWinds.Changesets/CHANGELOG.md index 9491c4b..ef9a9b2 100644 --- a/src/SolarWinds.Changesets/CHANGELOG.md +++ b/src/SolarWinds.Changesets/CHANGELOG.md @@ -1,17 +1,44 @@ # SolarWinds.Changesets +## 0.1.3 + +**Patch Changes**: + +- [[#6](https://github.com/solarwinds/net-changesets/issues/6)] Add Contributor Covenant Code of Conduct ([PR #14](https://github.com/solarwinds/net-changesets/pull/14)) +- [[#12](https://github.com/solarwinds/net-changesets/issues/12)] Add `-v, --version` option to changeset CLI and other small fixes ([PR #15](https://github.com/solarwinds/net-changesets/pull/15)) + - Replace package icon with transparent one + - Fix `MD012/no-multiple-blanks` linter warning in generated changelog that was caused by two new lines at the bottom of a generated section instead of one + - Fix `MD024/no-duplicate-heading` linter warning in generated changelog that was caused by duplicate headings. This `### {bumpType} Changes` was replaced with this `**{bumpType} Changes**:` + - Use absolute links to Markdown documents instead of relative ones, which may not work in some contexts (e.g., NuGet package page) + - `TreatWarningsAsErrors` is changing code style warnings to errors, but we want to keep them as warnings + - Remove leftover code for 'not initialized' error message and improve current 'not initialized' error message + - Fix formatting of JSON, YML, XML and Markdown files to use 2-space indentation + ## 0.1.2 **Patch Changes**: -- Improved error handling when the repository is uninitialized. +- [NO-ISSUE] Add security info file ([PR #5](https://github.com/solarwinds/net-changesets/pull/5)) +- [[#9](https://github.com/solarwinds/net-changesets/issues/9)] Copilot instructions and minor improvements for first start ([PR #10](https://github.com/solarwinds/net-changesets/pull/10)) +- [[#8](https://github.com/solarwinds/net-changesets/issues/8)] Improved error handling when the changeset is not initialized. ([PR #11](https://github.com/solarwinds/net-changesets/pull/11)) ## 0.1.1 **Patch Changes**: -- Metadata and icon improvements +- [[#3](https://github.com/solarwinds/net-changesets/issues/3)] Add missing nuget package metadata ([PR #4](https://github.com/solarwinds/net-changesets/pull/4)) ## 0.1.0 -First release. +**Minor Changes**: + +- [[#1](https://github.com/solarwinds/net-changesets/issues/1)] Initial release of .NET Changesets CLI tool ([PR #2](https://github.com/solarwinds/net-changesets/pull/2)) + - USAGE: `changeset [OPTIONS] [COMMAND]` + - Currently Supported Options: + - `-h, --help` + - Currently Supported Commands: + - `init` + - `add` + - `version` + - `publish` + - `status` diff --git a/src/SolarWinds.Changesets/Commands/Add/AddChangesetCommand.cs b/src/SolarWinds.Changesets/Commands/Add/AddChangesetCommand.cs index 907b09c..96b0aab 100644 --- a/src/SolarWinds.Changesets/Commands/Add/AddChangesetCommand.cs +++ b/src/SolarWinds.Changesets/Commands/Add/AddChangesetCommand.cs @@ -65,7 +65,7 @@ IChangesetsRepository changesetsRepository /// public override async Task ExecuteCommandAsync(CommandContext context) { - string[] projectNames = _projectFileNamesLocator.GetProjectFileNames(Path.Combine(Constants.WorkingDirectoryFullPath, ChangesetConfig.SourcePath)); + string[] projectNames = _projectFileNamesLocator.GetProjectFileNames(Path.Join(Constants.WorkingDirectoryFullPath, ChangesetConfig.SourcePath)); string[] selectedProjects = _console.Prompt( new MultiSelectionPrompt() @@ -104,7 +104,7 @@ private static string GetChangesetFileFullPath(string changesetFolderPath) { string changesetFileName = GenerateRandomWord(15); string changesetFileNameWithExtension = Path.ChangeExtension(changesetFileName, MarkDownExtension); - changesetFileFullPath = Path.Combine(changesetFolderPath, changesetFileNameWithExtension); + changesetFileFullPath = Path.Join(changesetFolderPath, changesetFileNameWithExtension); } while (File.Exists(changesetFileFullPath)); return changesetFileFullPath; diff --git a/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs b/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs index 0886107..b0b2b53 100644 --- a/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs +++ b/src/SolarWinds.Changesets/Commands/Publish/PublishChangesetCommand.cs @@ -57,7 +57,7 @@ public override async Task ExecuteCommandAsync(CommandContext context) foreach (string csharpProjectName in changedCsharpProjectNames) { - string csharpProjectFullPath = Path.Combine(Constants.WorkingDirectoryFullPath, csharpProjectName); + string csharpProjectFullPath = Path.Join(Constants.WorkingDirectoryFullPath, csharpProjectName); ProcessOutput packOutput = await _dotnetService.Pack(csharpProjectFullPath); diff --git a/src/SolarWinds.Changesets/Commands/Version/Helpers/ChangelogFileWriter.cs b/src/SolarWinds.Changesets/Commands/Version/Helpers/ChangelogFileWriter.cs index ca15aac..2b30a6a 100644 --- a/src/SolarWinds.Changesets/Commands/Version/Helpers/ChangelogFileWriter.cs +++ b/src/SolarWinds.Changesets/Commands/Version/Helpers/ChangelogFileWriter.cs @@ -23,7 +23,10 @@ public async Task GenerateChangelogFilesAsync(IEnumerable modul } else { - await File.WriteAllLinesAsync(moduleChangelogFilePath, [$"# {moduleChangelog.ModuleName}", string.Empty, changelogEntry]); + await File.WriteAllLinesAsync( + moduleChangelogFilePath, + [$"# {moduleChangelog.ModuleName}", string.Empty, changelogEntry.TrimEnd('\r', '\n')] // Trim end to avoid extra new lines at the end of the file + ); } } } @@ -39,12 +42,12 @@ private static string GenerateChangelogFile(ModuleChangelog moduleChangelog) BumpType = bumpToChangesGroup.Key, Descriptions = bumpToChangesGroup.Select(change => change.Description) }) - .OrderByDescending(bumpWithChanges => (int)bumpWithChanges.BumpType); + .OrderByDescending(bumpWithChanges => (int)bumpWithChanges.BumpType) + .ToList(); return $""" ## {newVersion} - {string.Join("", groupedDescriptions.Select(groupedDesc => GenerateChangelogBumpTypeSection(groupedDesc.BumpType, groupedDesc.Descriptions)) )} @@ -55,11 +58,11 @@ private static string GenerateChangelogBumpTypeSection(BumpType bumpType, IEnume { return $""" - ### {bumpType} Changes + + **{bumpType} Changes**: {string.Join(Environment.NewLine, descriptions.Select(description => $"- {description}"))} - - + """; } } diff --git a/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs b/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs index 99d8a2b..e7c5f2b 100644 --- a/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs +++ b/src/SolarWinds.Changesets/Commands/Version/Helpers/CsProjectsRepository.cs @@ -22,7 +22,7 @@ public CsProject[] GetCsProjects(ChangesetConfig changesetConfig) { List csProjects = []; - string[] csprojFilePaths = GetCsprojFilePaths(Path.Combine(Constants.WorkingDirectoryFullPath, changesetConfig.SourcePath)); + string[] csprojFilePaths = GetCsprojFilePaths(Path.Join(Constants.WorkingDirectoryFullPath, changesetConfig.SourcePath)); foreach (string csprojFilePath in csprojFilePaths) { diff --git a/src/SolarWinds.Changesets/Infrastructure/SpectreConsoleConfiguratorExtensions.cs b/src/SolarWinds.Changesets/Infrastructure/SpectreConsoleConfiguratorExtensions.cs new file mode 100644 index 0000000..7a87473 --- /dev/null +++ b/src/SolarWinds.Changesets/Infrastructure/SpectreConsoleConfiguratorExtensions.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using Spectre.Console.Cli; + +namespace SolarWinds.Changesets.Infrastructure; + +internal static class SpectreConsoleConfiguratorExtensions +{ + /// + /// Uses the version retrieved from the as the application's version. + /// + /// + /// The InformationalVersion contains version in following format: $(VersionPrefix)-$(VersionSuffix)+$(SourceRevisionId) + /// We are only interested in the $(VersionPrefix)-$(VersionSuffix) part, so we split by '+' and take the first part. + /// + /// The configurator. + /// A configurator that can be used to configure the application further. + public static IConfigurator UseAssemblyInformationalVersionWithoutSourceRevisionId(this IConfigurator configurator) + { + ArgumentNullException.ThrowIfNull(configurator); + + configurator.Settings.ApplicationVersion = Assembly + .GetEntryAssembly() + ?.GetCustomAttribute() + ?.InformationalVersion + ?.Split('+')[0] ?? "Failed to retrieve version."; + + return configurator; + } +} diff --git a/src/SolarWinds.Changesets/Program.cs b/src/SolarWinds.Changesets/Program.cs index 1e9d247..c0143d6 100644 --- a/src/SolarWinds.Changesets/Program.cs +++ b/src/SolarWinds.Changesets/Program.cs @@ -31,6 +31,7 @@ { config .SetApplicationName("changeset") + .UseAssemblyInformationalVersionWithoutSourceRevisionId() .SetExceptionHandler(ExceptionHandler.Handle); config diff --git a/src/SolarWinds.Changesets/Services/DotnetService.cs b/src/SolarWinds.Changesets/Services/DotnetService.cs index e39d04c..af63dae 100644 --- a/src/SolarWinds.Changesets/Services/DotnetService.cs +++ b/src/SolarWinds.Changesets/Services/DotnetService.cs @@ -34,7 +34,7 @@ public async Task Publish(string packageSource) throw new ArgumentException("Source path cannot be null or empty.", nameof(packageSource)); } - string dotnetCommand = $"nuget push {Path.Combine(Constants.NupkgOutputFullPath, "*.nupkg")}"; + string dotnetCommand = $"nuget push {Path.Join(Constants.NupkgOutputFullPath, "*.nupkg")}"; dotnetCommand += $" --source {packageSource}"; diff --git a/src/SolarWinds.Changesets/Shared/ConsoleExtensions.cs b/src/SolarWinds.Changesets/Shared/ConsoleExtensions.cs index a6d829e..b12e647 100644 --- a/src/SolarWinds.Changesets/Shared/ConsoleExtensions.cs +++ b/src/SolarWinds.Changesets/Shared/ConsoleExtensions.cs @@ -4,17 +4,6 @@ namespace SolarWinds.Changesets.Shared; internal static class ConsoleExtensions { - public static void ChangesetsIsNotInitialized(this IAnsiConsole console) - { - const string errorMessage = - """ - There is no .changeset folder. - If this is the first time `changesets` have been used in this project, run `net-changeset init` to get set up. - If you expect changesets to exist, check the Git history for when the folder was removed to ensure the configuration is not lost. - """; - console.MarkupLine($"[red]error[/] {errorMessage}"); - } - /// /// Prints a list of messages to the console, optionally applying a Spectre.Console markup style to each message. /// diff --git a/src/SolarWinds.Changesets/Shared/Constants.cs b/src/SolarWinds.Changesets/Shared/Constants.cs index e6f5e33..ff78376 100644 --- a/src/SolarWinds.Changesets/Shared/Constants.cs +++ b/src/SolarWinds.Changesets/Shared/Constants.cs @@ -28,15 +28,15 @@ public static class Constants /// /// Gets the full path to the changeset directory. /// - public static string ChangesetDirectoryFullPath { get; } = Path.Combine(WorkingDirectoryFullPath, ChangesetDirectoryName); + public static string ChangesetDirectoryFullPath { get; } = Path.Join(WorkingDirectoryFullPath, ChangesetDirectoryName); /// /// Gets the full path to the changeset configuration file. /// - public static string ChangesetConfigFileFullPath { get; } = Path.Combine(ChangesetDirectoryFullPath, ConfigFileName); + public static string ChangesetConfigFileFullPath { get; } = Path.Join(ChangesetDirectoryFullPath, ConfigFileName); /// /// Gets the full path to the directory where NuGet packages are output. /// - public static string NupkgOutputFullPath { get; } = Path.Combine(WorkingDirectoryFullPath, "nupkg"); + public static string NupkgOutputFullPath { get; } = Path.Join(WorkingDirectoryFullPath, "nupkg"); } diff --git a/src/SolarWinds.Changesets/Shared/ExceptionHandler.cs b/src/SolarWinds.Changesets/Shared/ExceptionHandler.cs index 8924e8e..59d4a8c 100644 --- a/src/SolarWinds.Changesets/Shared/ExceptionHandler.cs +++ b/src/SolarWinds.Changesets/Shared/ExceptionHandler.cs @@ -11,11 +11,10 @@ public static int Handle(Exception ex, ITypeResolver? _) if (ex is InitializationException) { - returnCode = ResultCodes.NotInitialized; - AnsiConsole.MarkupLine("[red]Error:[/] The changesets tool is not initialized in this repository."); + AnsiConsole.MarkupLine("[red]error[/] The [yellow]changeset[/] tool is not initialized in this repository."); AnsiConsole.WriteLine(); - AnsiConsole.MarkupLine("Please run [yellow]changeset init[/] first to configure the tool."); - return returnCode; + AnsiConsole.MarkupLine("Please run [yellow]changeset init[/] first in the root of the repository to configure the tool."); + return ResultCodes.NotInitialized; } AnsiConsole.WriteException(ex); diff --git a/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj b/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj index 35acf7a..68b08ae 100644 --- a/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj +++ b/src/SolarWinds.Changesets/SolarWinds.Changesets.csproj @@ -1,4 +1,4 @@ - + @@ -17,30 +17,30 @@ $(RepositoryUrl)/releases true Exe - 0.1.2 + 0.1.3 embedded true - - - - + + + + - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + - - - + + + diff --git a/tests/SolarWinds.Changesets.Tests/SolarWinds.Changesets.Tests.csproj b/tests/SolarWinds.Changesets.Tests/SolarWinds.Changesets.Tests.csproj index 0876afb..87d24dc 100644 --- a/tests/SolarWinds.Changesets.Tests/SolarWinds.Changesets.Tests.csproj +++ b/tests/SolarWinds.Changesets.Tests/SolarWinds.Changesets.Tests.csproj @@ -1,57 +1,57 @@  - - false - true - + + false + true + - - - + + + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + - - - + + + - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + diff --git a/tests/SolarWinds.Changesets.Tests/Version/ChangelogFileWriterTests.cs b/tests/SolarWinds.Changesets.Tests/Version/ChangelogFileWriterTests.cs index d91ca8c..25ffd90 100644 --- a/tests/SolarWinds.Changesets.Tests/Version/ChangelogFileWriterTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Version/ChangelogFileWriterTests.cs @@ -8,7 +8,7 @@ namespace SolarWinds.Changesets.Tests.Version; [TestFixture] internal sealed class ChangelogFileWriterTests { - private static readonly string s_changelogFilePath = Path.Combine(Environment.CurrentDirectory, Constants.ChangelogFileName); + private static readonly string s_changelogFilePath = Path.Join(Environment.CurrentDirectory, Constants.ChangelogFileName); [Test] public async Task GenerateChangelogFilesAsync_GeneratesNewFile_WhenFileDoesNotExist() @@ -18,23 +18,21 @@ public async Task GenerateChangelogFilesAsync_GeneratesNewFile_WhenFileDoesNotEx IEnumerable changes = [ new ModuleChangelog() { - ModuleName = "Changesets.Module", + ModuleName = "Project", CurrentVersion = new Semver(1, 0, 0), - ModuleCsProjFilePath = Path.Combine(Environment.CurrentDirectory, "project.csproj"), + ModuleCsProjFilePath = Path.Join(Environment.CurrentDirectory, "project.csproj"), Changes = [("Something has changed", BumpType.Minor)] }]; string expectedContent = """ -# Changesets.Module +# Project ## 1.1.0 -### Minor Changes +**Minor Changes**: - Something has changed - - """; await fileWriter.GenerateChangelogFilesAsync(changes); @@ -52,24 +50,60 @@ public async Task GenerateChangelogFilesAsync_AmendsExistingFile_WhenFileExists( IEnumerable changes = [ new ModuleChangelog() { - ModuleName = "Changeset.Tests", + ModuleName = "Project", CurrentVersion = new Semver(1, 0, 0), - ModuleCsProjFilePath = Path.Combine(Environment.CurrentDirectory, "project.csproj"), + ModuleCsProjFilePath = Path.Join(Environment.CurrentDirectory, "project.csproj"), Changes = [ ("change1", BumpType.Minor), ("change2", BumpType.Major) ] }]; + IEnumerable changes2 = [ + new ModuleChangelog() + { + ModuleName = "Project", + CurrentVersion = new Semver(2, 0, 0), + ModuleCsProjFilePath = Path.Join(Environment.CurrentDirectory, "project.csproj"), + Changes = [ + ("change3", BumpType.Minor), + ("change4", BumpType.Major) + ] + }]; + + string expectedContent = """ +# Project + +## 3.0.0 + +**Major Changes**: + +- change4 + +**Minor Changes**: + +- change3 + +## 2.0.0 + +**Major Changes**: + +- change2 + +**Minor Changes**: + +- change1 + +"""; + await fileWriter.GenerateChangelogFilesAsync(changes); File.Exists(s_changelogFilePath).Should().BeTrue(); - long fileSize = new FileInfo(s_changelogFilePath).Length; - await fileWriter.GenerateChangelogFilesAsync(changes); + await fileWriter.GenerateChangelogFilesAsync(changes2); File.Exists(s_changelogFilePath).Should().BeTrue(); - new FileInfo(s_changelogFilePath).Length.Should().BeGreaterThan(fileSize); + AssertChangelogContent(expectedContent); } [Test] @@ -80,9 +114,9 @@ public async Task GenerateChangelogFilesAsync_GeneratesTwoChangelogs_WhenChangel IEnumerable changes = [ new ModuleChangelog() { - ModuleName = "Changeset.Tests", + ModuleName = "Project", CurrentVersion = new Semver(1, 0, 0), - ModuleCsProjFilePath = Path.Combine(Environment.CurrentDirectory, "project.csproj"), + ModuleCsProjFilePath = Path.Join(Environment.CurrentDirectory, "project.csproj"), Changes = [ ("change1", BumpType.Minor), ("change2", BumpType.Major) @@ -90,36 +124,34 @@ public async Task GenerateChangelogFilesAsync_GeneratesTwoChangelogs_WhenChangel }, new ModuleChangelog() { - ModuleName = "Changeset.Tests", + ModuleName = "Project1", CurrentVersion = new Semver(1, 0, 0), - ModuleCsProjFilePath = Path.Combine(Environment.CurrentDirectory, "TestData", "project1.csproj"), + ModuleCsProjFilePath = Path.Join(Environment.CurrentDirectory, "TestData", "project1.csproj"), Changes = [ - ("change1", BumpType.Minor), - ("change2", BumpType.Major) + ("change3", BumpType.Minor), + ("change4", BumpType.Major) ] }]; string expectedContent = """ -# Changeset.Tests +# Project ## 2.0.0 -### Major Changes +**Major Changes**: - change2 -### Minor Changes +**Minor Changes**: - change1 - - """; await fileWriter.GenerateChangelogFilesAsync(changes); File.Exists(s_changelogFilePath).Should().BeTrue(); - File.Exists(Path.Combine(changes.Last().ModuleDirectoryPath, Constants.ChangelogFileName)).Should().BeTrue(); + File.Exists(Path.Join(changes.Last().ModuleDirectoryPath, Constants.ChangelogFileName)).Should().BeTrue(); AssertChangelogContent(expectedContent); } @@ -132,14 +164,14 @@ public void TearDown() { foreach (string file in mdFiles) { - File.Delete(Path.Combine(Environment.CurrentDirectory, file)); + File.Delete(file); } } } private static void AssertChangelogContent(string expectedChangelogContent) { - string actualContent = File.ReadAllText(Path.Combine(Environment.CurrentDirectory, Constants.ChangelogFileName)); + string actualContent = File.ReadAllText(Path.Join(Environment.CurrentDirectory, Constants.ChangelogFileName)); actualContent.Should().Be(expectedChangelogContent); } diff --git a/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs b/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs index 56277f2..324d586 100644 --- a/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Version/CsProjectsRepositoryTests.cs @@ -11,9 +11,9 @@ namespace SolarWinds.Changesets.Tests.Version; [TestFixture] internal sealed class CsProjectsRepositoryTests { - private static readonly string s_testProjectWithVersion = Path.Combine(Environment.CurrentDirectory, "TestData", "SubDirectory", "TestProjectWithVersion.csproj"); - private static readonly string s_testProjectWithoutVersion = Path.Combine(Environment.CurrentDirectory, "TestData", "TestProjectWithoutVersion.csproj"); - private static readonly string s_testFilePath = Path.Combine(Environment.CurrentDirectory, "TestData", "test.csproj"); + private static readonly string s_testProjectWithVersion = Path.Join(Environment.CurrentDirectory, "TestData", "SubDirectory", "TestProjectWithVersion.csproj"); + private static readonly string s_testProjectWithoutVersion = Path.Join(Environment.CurrentDirectory, "TestData", "TestProjectWithoutVersion.csproj"); + private static readonly string s_testFilePath = Path.Join(Environment.CurrentDirectory, "TestData", "test.csproj"); [TearDown] public void TearDown() @@ -81,7 +81,7 @@ public void GetCsProjects_OneVersionOneWithoutVersionProjects_ReturnsTwoProjects { ChangesetConfig config = new() { - SourcePath = Path.Combine(Environment.CurrentDirectory, "TestData") + SourcePath = "TestData" }; TestConsole testConsole = new(); diff --git a/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs b/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs index a923e87..2ae67e6 100644 --- a/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs +++ b/tests/SolarWinds.Changesets.Tests/Version/VersionChangesetCommandTests.cs @@ -21,7 +21,7 @@ internal sealed class VersionChangesetCommandTests [SetUp] public void SetUp() { - string testDataDirectoryFullPath = Path.Combine(Environment.CurrentDirectory, "Version", "TestData"); + string testDataDirectoryFullPath = Path.Join(Environment.CurrentDirectory, "Version", "TestData"); CopyDirectory(testDataDirectoryFullPath, Environment.CurrentDirectory, true, true); } @@ -63,15 +63,15 @@ public void VersionCommand_HappyPath_FolderAndFilesAreCreated() private static void AssertVersionOutput() { //changesetfile is deleted - File.Exists(Path.Combine(Environment.CurrentDirectory, ChangesetFolder, ChangesetFile)).Should().BeFalse(); + File.Exists(Path.Join(Environment.CurrentDirectory, ChangesetFolder, ChangesetFile)).Should().BeFalse(); //project files are updated - LoadVersionFromProjectFile(Path.Combine(Environment.CurrentDirectory, SrcFolder, "ProjectA", "ProjectA.csproj")).Should().BeEquivalentTo(new Semver(1, 0, 2)); - LoadVersionFromProjectFile(Path.Combine(Environment.CurrentDirectory, SrcFolder, "ProjectB", "ProjectB.csproj")).Should().BeEquivalentTo(new Semver(1, 1, 0)); + LoadVersionFromProjectFile(Path.Join(Environment.CurrentDirectory, SrcFolder, "ProjectA", "ProjectA.csproj")).Should().BeEquivalentTo(new Semver(1, 0, 2)); + LoadVersionFromProjectFile(Path.Join(Environment.CurrentDirectory, SrcFolder, "ProjectB", "ProjectB.csproj")).Should().BeEquivalentTo(new Semver(1, 1, 0)); //changelogs are created - File.Exists(Path.Combine(Environment.CurrentDirectory, SrcFolder, "ProjectA", ChangelogFile)).Should().BeTrue(); - File.Exists(Path.Combine(Environment.CurrentDirectory, SrcFolder, "ProjectB", ChangelogFile)).Should().BeTrue(); + File.Exists(Path.Join(Environment.CurrentDirectory, SrcFolder, "ProjectA", ChangelogFile)).Should().BeTrue(); + File.Exists(Path.Join(Environment.CurrentDirectory, SrcFolder, "ProjectB", ChangelogFile)).Should().BeTrue(); } private static Semver? LoadVersionFromProjectFile(string path) @@ -102,7 +102,7 @@ private static void CopyDirectory(string sourceDirectory, string destinationDire foreach (FileInfo file in directoryInfo.GetFiles()) { - string targetFilePath = Path.Combine(destinationDirectory, file.Name); + string targetFilePath = Path.Join(destinationDirectory, file.Name); file.CopyTo(targetFilePath); } @@ -110,7 +110,7 @@ private static void CopyDirectory(string sourceDirectory, string destinationDire { foreach (DirectoryInfo subDir in dirs) { - string newDestinationDir = Path.Combine(destinationDirectory, subDir.Name); + string newDestinationDir = Path.Join(destinationDirectory, subDir.Name); CopyDirectory(subDir.FullName, newDestinationDir, true, false); } }