{"id":13592414,"url":"https://github.com/sharpliner/sharpliner","last_synced_at":"2025-10-06T08:14:16.288Z","repository":{"id":37494660,"uuid":"356900727","full_name":"sharpliner/sharpliner","owner":"sharpliner","description":"Use C# instead of YAML to define your Azure DevOps pipelines","archived":false,"fork":false,"pushed_at":"2025-05-09T11:29:22.000Z","size":1211,"stargazers_count":313,"open_issues_count":29,"forks_count":30,"subscribers_count":11,"default_branch":"main","last_synced_at":"2025-05-09T11:56:41.802Z","etag":null,"topics":["azure-devops","azure-devops-pipelines","csharp","dotnet","pipelines","yaml"],"latest_commit_sha":null,"homepage":"https://www.nuget.org/packages/Sharpliner/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sharpliner.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-04-11T15:10:38.000Z","updated_at":"2025-05-09T11:27:51.000Z","dependencies_parsed_at":"2023-11-14T20:29:54.130Z","dependency_job_id":"a802a0cd-0278-466d-9385-1099d092a987","html_url":"https://github.com/sharpliner/sharpliner","commit_stats":{"total_commits":203,"total_committers":9,"mean_commits":"22.555555555555557","dds":"0.11330049261083741","last_synced_commit":"714a369bba44001080f52e061acf9869cc522853"},"previous_names":[],"tags_count":76,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sharpliner%2Fsharpliner","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sharpliner%2Fsharpliner/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sharpliner%2Fsharpliner/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sharpliner%2Fsharpliner/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sharpliner","download_url":"https://codeload.github.com/sharpliner/sharpliner/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254007081,"owners_count":21998557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure-devops","azure-devops-pipelines","csharp","dotnet","pipelines","yaml"],"created_at":"2024-08-01T16:01:08.988Z","updated_at":"2025-10-06T08:14:16.278Z","avatar_url":"https://github.com/sharpliner.png","language":"C#","funding_links":[],"categories":["C#","C\\#"],"sub_categories":[],"readme":"[![Build Status](https://dev.azure.com/premun/Sharpliner/_apis/build/status/sharpliner-pr?branchName=main)](https://dev.azure.com/premun/Sharpliner/_build/latest?definitionId=6\u0026branchName=main) [![Nuget](https://img.shields.io/nuget/v/Sharpliner)](https://www.nuget.org/packages/Sharpliner/)\n\nSharpliner is a .NET library that lets you use C# for Azure DevOps pipeline definitions instead of YAML.\nExchange YAML indentation problems for the type-safe environment of C# and let IntelliSense speed up your work!\n\n- [Getting started](#getting-started)\n- [Example](#example)\n- [Sharpliner features](#sharpliner-features)\n  - [Intellisense](#intellisense)\n  - [Useful macros](#useful-macros)\n  - [Sourcing scripts from files](#sourcing-scripts-from-files)\n  - [Pipeline validation](#pipeline-validation)\n- [Something missing?](#something-missing)\n- [Developing Sharpliner](#developing-sharpliner)\n\n## Getting started\n\nAll you have to do is reference our [NuGet package](https://www.nuget.org/packages/Sharpliner/) in your project, override a class with your definition and `dotnet build` the project! Dead simple!\n\nFor more detailed steps, check our [documentation](https://github.com/sharpliner/sharpliner/blob/main/docs/AzureDevOps/GettingStarted.md).\n\n## Example\n\n```csharp\n// Just override prepared abstract classes and `dotnet build` the project, nothing else is needed!\n// For a full list of classes you can override\n//    see https://github.com/sharpliner/sharpliner/blob/main/src/Sharpliner/AzureDevOps/PublicDefinitions.cs\n// You can also generate collections of definitions dynamically\n//    see https://github.com/sharpliner/sharpliner/blob/main/docs/AzureDevOps/DefinitionCollections.md\nclass PullRequestPipeline : SingleStagePipelineDefinition\n{\n    // Say where to publish the YAML to\n    public override string TargetFile =\u003e \"eng/pr.yml\";\n    public override TargetPathType TargetPathType =\u003e TargetPathType.RelativeToGitRoot;\n\n    private static readonly Variable DotnetVersion = new(\"DotnetVersion\", string.Empty);\n\n    public override SingleStagePipeline Pipeline =\u003e new()\n    {\n        Pr = new PrTrigger(\"main\"),\n\n        Variables =\n        [\n            // YAML ${{ if }} conditions are available with handy macros that expand into the\n            // expressions such as comparing branch names. We also have \"else\"\n            If.IsBranch(\"net-6.0\")\n                .Variable(DotnetVersion with { Value = \"6.0.100\" })\n                .Group(\"net6-keyvault\")\n            .Else\n                .Variable(DotnetVersion with { Value = \"5.0.202\" }),\n        ],\n\n        Jobs =\n        [\n            new Job(\"Build\")\n            {\n                Pool = new HostedPool(\"Azure Pipelines\", \"windows-latest\"),\n                Steps =\n                [\n                    // Many tasks have helper methods for shorter notation\n                    DotNet.Install.Sdk(DotnetVersion),\n\n                    NuGet.Authenticate([\"myServiceConnection\"]),\n\n                    // You can also specify any pipeline task in full too\n                    Task(\"DotNetCoreCLI@2\", \"Build and test\") with\n                    {\n                        Inputs = new()\n                        {\n                            { \"command\", \"test\" },\n                            { \"projects\", \"src/MyProject.sln\" },\n                        }\n                    },\n\n                    // Frequently used ${{ if }} statements have readable macros\n                    If.IsPullRequest\n                        // You can load script contents from a .ps1 file and inline them into YAML\n                        // This way you can write scripts with syntax highlighting separately\n                        .Step(Powershell.FromResourceFile(\"New-Report.ps1\", \"Create build report\")),\n                ]\n            }\n        ],\n    };\n}\n```\n\n## Sharpliner features\n\nApart from the obvious benefits of using a static type language with IDE support, not having to have to deal with indentation problems ever again, being able to split the code easily or the ability to generate YAML programmatically, there are several other benefits of using Sharpliner.\n\n### Intellisense\n\nOne of the best things when using Sharpliner is that you won't have to go to the YAML reference every time you're adding a new piece of your pipeline.\nHaving everything strongly typed will allow your IDE to give you hints all the way!\n\n![Example intellisense for pipeline variables](https://raw.githubusercontent.com/sharpliner/sharpliner/main/docs/images/variables-intellisense.png)\n\n### Nice APIs\n\nImagine you want to install the .NET SDK. For that, Azure Pipelines have the `DotNetCoreCLI@2` task.\nHowever, this task's specification is quite long since the task does many things:\n\n```yaml\n# .NET Core\n# Build, test, package, or publish a dotnet application, or run a custom dotnet command\n# https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/dotnet-core-cli?view=azure-devops\n- task: DotNetCoreCLI@2\n  inputs:\n    command: 'build' # Options: build, push, pack, publish, restore, run, test, custom\n    publishWebProjects: true # Required when command == Publish\n    projects: # Optional\n    custom: # Required when command == Custom\n    arguments: # Optional\n    publishTestResults: true # Optional\n    testRunTitle: # Optional\n    zipAfterPublish: true # Optional\n    modifyOutputPath: true # Optional\n    feedsToUse: 'select' # Options: select, config\n    vstsFeed: # Required when feedsToUse == Select\n    feedRestore: # Required when command == restore. projectName/feedName for project-scoped feed. FeedName only for organization-scoped feed.\n    includeNuGetOrg: true # Required when feedsToUse == Select\n    nugetConfigPath: # Required when feedsToUse == Config\n    externalFeedCredentials: # Optional\n    noCache: false\n    restoreDirectory:\n    restoreArguments: # Optional\n    verbosityRestore: 'Detailed' # Options: -, quiet, minimal, normal, detailed, diagnostic\n    packagesToPush: '$(Build.ArtifactStagingDirectory)/*.nupkg' # Required when command == Push\n    nuGetFeedType: 'internal' # Required when command == Push# Options: internal, external\n    publishVstsFeed: # Required when command == Push \u0026\u0026 NuGetFeedType == Internal\n    publishPackageMetadata: true # Optional\n    publishFeedCredentials: # Required when command == Push \u0026\u0026 NuGetFeedType == External\n    packagesToPack: '**/*.csproj' # Required when command == Pack\n    packDirectory: '$(Build.ArtifactStagingDirectory)' # Optional\n    nobuild: false # Optional\n    includesymbols: false # Optional\n    includesource: false # Optional\n    versioningScheme: 'off' # Options: off, byPrereleaseNumber, byEnvVar, byBuildNumber\n    versionEnvVar: # Required when versioningScheme == byEnvVar\n    majorVersion: '1' # Required when versioningScheme == ByPrereleaseNumber\n    minorVersion: '0' # Required when versioningScheme == ByPrereleaseNumber\n    patchVersion: '0' # Required when versioningScheme == ByPrereleaseNumber\n    buildProperties: # Optional\n    verbosityPack: 'Detailed' # Options: -, quiet, minimal, normal, detailed, diagnostic\n    workingDirectory:\n```\n\nNotice how some of the properties are only valid in a specific combination with another.\nWith Sharpliner, we remove some of this complexity using nice fluent APIs:\n\n```csharp\nDotNet.Install.Sdk(parameters[\"version\"]),\n\nDotNet.Restore.FromFeed(\"dotnet-7-preview-feed\", includeNuGetOrg: false) with\n{\n    ExternalFeedCredentials = \"feeds/dotnet-7\",\n    NoCache = true,\n    RestoreDirectory = \".packages\",\n},\n\nDotNet.Build(\"src/MyProject.csproj\") with\n{\n    Timeout = TimeSpan.FromMinutes(20)\n},\n```\n\n### Useful macros\n\nSome very common pipeline patterns such as comparing the current branch name or detecting pull requests are very cumbersome to do in YAML (long conditions full of complicated `${{ if }}` syntax).\nFor many of these, we have handy macros so that you get more readable and shorter code.\n\nFor example this YAML\n\n```yaml\n- ${{ if eq(variables['Build.SourceBranch'], 'refs/heads/production') }}:\n  - name: rg-suffix\n    value: -pr\n\n- ${{ else }}:\n  - name: rg-suffix\n    value: -prod\n```\n\ncan become this C#\n\n```csharp\nIf.IsBranch(\"production\")\n    .Variable(\"rg-suffix\", \"-pr\")\n.Else\n    .Variable(\"rg-suffix\", \"-prod\")\n```\n\n### Re-usable pipeline blocks\n\nSharpliner lets you reuse code more easily than YAML templates do.\nApart from obvious C# code reuse, you can also define sets of C# building blocks and reuse them in your pipelines:\n\n```csharp\nclass ProjectBuildSteps : StepLibrary\n{\n    public override List\u003cAdoExpression\u003cStep\u003e\u003e Steps =\u003e\n    [\n        DotNet.Install.Sdk(\"6.0.100\"),\n\n        If.IsBranch(\"main\")\n            .Step(DotNet.Restore.Projects(\"src/MyProject.sln\")),\n\n        DotNet.Build(\"src/MyProject.sln\"),\n    ];\n}\n```\n\nYou can then reference this library in between build steps and it will get expanded into the pipeline's YAML:\n\n```csharp\nnew Job(\"Build\")\n{\n    Steps =\n    {\n        Script.Inline(\"echo 'Hello World'\"),\n\n        StepLibrary\u003cProjectBuildSteps\u003e(),\n\n        Script.Inline(\"echo 'Goodbye World'\"),\n    }\n}\n```\n\nMore about this feature can be found [here (DefinitionLibraries.md)](https://github.com/sharpliner/sharpliner/blob/main/docs/AzureDevOps/DefinitionLibraries.md).\n\n### Sourcing scripts from files\n\nWhen you need to add cmd, PowerShell or bash steps into your pipeline, maintaining these bits inside YAML can be error prone.\nWith Sharpliner you can keep scripts in their own files (`.ps1`, `.sh`..) where you get the natural environment you're used to such as syntax highlighting.\nSharpliner gives you APIs to load these at build time and include them inline:\n\n```csharp\nSteps =\n{\n    Bash.FromResourceFile(\"embedded-script.sh\") with\n    {\n        DisplayName = \"Run post-build clean-up\",\n        Timeout = TimeSpan.FromMinutes(5),\n    }\n}\n```\n\n### Correct variable/parameter types\n\nA frequent struggle people have with Azure pipelines is using the [right type of variable](https://docs.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops\u0026tabs=yaml%2Cbatch#understand-variable-syntax) in the right context.\nBe it a `${{ compile time parameter }}`, a `variable['used in runtime']` or a `$(macro)` syntax, with Sharpliner you won't have to worry about which one to pick as it understands the context and selects the right one for you.\n\n### Pipeline validation\n\nYour pipeline definition can be validated during publishing and you can uncover issues, such as typos inside `dependsOn`, you would only find by trying to run the pipeline in CI.\nThis gives you a faster dev loop and greater productivity.\n\nWe are continuously adding new validations as we find new error-prone spots.\nEach validation can be individually configured/silenced in case you don't wish to take advantage of these:\n\n```csharp\nclass YourCustomConfiguration : SharplinerConfiguration\n{\n    public override void Configure()\n    {\n        // You can set severity for various validations\n        Validations.DependsOnFields = ValidationSeverity.Off;\n        Validations.NameFields = ValidationSeverity.Warning;\n\n        // You can also further customize serialization\n        Serialization.PrettifyYaml = false;\n        Serialization.UseElseExpression = true;\n        Serialization.IncludeHeaders = false;\n\n        // You can add hooks that execute during the publish process\n        Hooks.BeforePublish = (definition, path) =\u003e {};\n        Hooks.AfterPublish = (definition, path, yaml) =\u003e {};\n    }\n}\n```\n\n## Something missing?\n\nIf you find a missing feature / API / property / use case, file an issue in the project's repository.\nWe try to be very responsive and for small asks can deliver you a new version very fast.\n\nIf you want to start contributing, either you already know about something missing or you can choose from some of the open issues.\nWe will help you review your first change so that you can continue with something advanced!\n\nAnother way to start is to try out Sharpliner to define your own, already existing pipeline.\nThis way you can uncover missing features or you can introduce shortcuts for definitions of build tasks or similar that you use frequently.\nContributions like these are also very welcome!\nIn these cases, it is worth starting with describing your intent in an issue first.\n\n## Developing Sharpliner\n\nContributions are very welcome and if you find yourself opening the codebase there are couple of things you should know.\nThe repository layout is quite simple:\n\n```bash\n.\n├── artifacts            # All build outputs go here. Nuke it to clean\n├── docs                 # Documentation\n├── eng                  # CI/CD for the repo\n│   ├── Sharpliner.CI    # C# definitions for pipelines of this repo\n│   ├── DocsGenerator    # C# tool to generate the documentation markdown files\n│   ├── scripts          # scripts used by the CI \u0026 e2e tests\n│   └── pipelines        # YAML pipelines of the repository\n├── src\n│   └── Sharpliner       # Main Sharpliner project\n│       └── build        # Targets/props for the Sharpliner .nupkg\n├── tests\n│   ├── E2E.Tests        # E2E tests using the Sharpliner  .nupkg\n│   └── Sharpliner.Tests # Unit tests for the main Sharpliner project\n└── Sharpliner.sln       # Main solution of the project\n```\n\nDeveloping is quite easy - open the `Sharpliner.sln` solution in VS. However, the solution won't build 100% the first time.\nThis is because of the `Sharpliner.CI` project.\nThis projects uses Sharpliner and defines pipelines for the Sharpliner repository - the YAML is published to `eng/pipelines`.\nThis way we test quite many Sharpliner features right in the PR build.\nThe `Sharpliner.CI` project expects that a package `Sharpliner.43.43.43.nupkg` is built locally which it then references it simulating the real usage of Sharpliner from `nuget.org`.\n\nTo build all of the solution 100%, **you have to build `Sharpliner.CI` from console** as building inside VS won't work on cold checkout.\nThis will package `Sharpliner.csproj` first and produce the `43.43.43` package:\n\n```text\n\u003e dotnet build eng/Sharpliner.CI/Sharpliner.CI.csproj\n```\n\nIf you make changes to the main library and want to test it using `Sharpliner.CI`, clean and then build the CI project from console:\n\n```text\n\u003e dotnet clean eng/Sharpliner.CI/Sharpliner.CI.csproj\n\u003e dotnet build eng/Sharpliner.CI/Sharpliner.CI.csproj\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsharpliner%2Fsharpliner","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsharpliner%2Fsharpliner","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsharpliner%2Fsharpliner/lists"}