├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── LICENSE ├── README.md ├── action.yml └── index.js /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Create a bug report to help improve this action 4 | title: "[BUG] " 5 | labels: bug 6 | assignees: brandedoutcast 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Failed Action Log URL (Required)** 14 | A URL to the failed action log. 15 | 16 | **To Reproduce** 17 | Steps to reproduce the behavior: 18 | 1. 19 | 20 | **Expected Behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Environment:** 24 | - Platform [e.g. Windows / Linux] 25 | - Action Version [e.g. v2.5.3] 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this action 4 | title: "[FEATURE] " 5 | labels: enhancement 6 | assignees: brandedoutcast 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rohith Reddy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ✨ Publish NuGet 2 | GitHub action to build, pack & publish nuget packages automatically when a project version is updated 3 | 4 | ## Usage 5 | Create new `.github/workflows/publish.yml` file: 6 | 7 | ```yml 8 | name: publish to nuget 9 | on: 10 | push: 11 | branches: 12 | - master # Default release branch 13 | jobs: 14 | publish: 15 | name: build, pack & publish 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | # - name: Setup dotnet 21 | # uses: actions/setup-dotnet@v1 22 | # with: 23 | # dotnet-version: 6.0.0 24 | 25 | # Publish 26 | - name: publish on version change 27 | id: publish_nuget 28 | uses: alirezanet/publish-nuget@v3.1.0 29 | with: 30 | # Filepath of the project to be packaged, relative to root of repository 31 | PROJECT_FILE_PATH: Core/Core.csproj 32 | 33 | # NuGet package id, used for version detection & defaults to project name 34 | # PACKAGE_NAME: Core 35 | 36 | # Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH 37 | # VERSION_FILE_PATH: Directory.Build.props 38 | 39 | # Regex pattern to extract version info in a capturing group 40 | # VERSION_REGEX: ^\s*(.*)<\/Version>\s*$ 41 | 42 | # Useful with external providers like Nerdbank.GitVersioning, ignores VERSION_FILE_PATH & VERSION_REGEX 43 | # VERSION_STATIC: 1.0.0 44 | 45 | # Flag to toggle git tagging, enabled by default 46 | # TAG_COMMIT: true 47 | 48 | # Format of the git tag, [*] gets replaced with actual version 49 | # TAG_FORMAT: v* 50 | 51 | # API key to authenticate with NuGet server 52 | # NUGET_KEY: ${{secrets.NUGET_API_KEY}} 53 | 54 | # NuGet server uri hosting the packages, defaults to https://api.nuget.org 55 | # NUGET_SOURCE: https://api.nuget.org 56 | 57 | # Flag to toggle pushing symbols along with nuget package to the server, disabled by default 58 | # INCLUDE_SYMBOLS: false 59 | 60 | # Flag to toggle not building the project and letting pack command handle restoring & building, disabled by default 61 | # NO_BUILD: false 62 | ``` 63 | 64 | - Project gets published only if there's a `NUGET_KEY` configured in the repository 65 | 66 | ## Inputs 67 | 68 | Input | Default Value | Description 69 | --- | --- | --- 70 | PROJECT_FILE_PATH | | Filepath of the project to be packaged, relative to root of repository 71 | PACKAGE_NAME | | NuGet package id, used for version detection & defaults to project name 72 | VERSION_FILE_PATH | `[PROJECT_FILE_PATH]` | Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH 73 | VERSION_REGEX | `^\s*(.*)<\/Version>\s*$` | Regex pattern to extract version info in a capturing group 74 | VERSION_STATIC| | Useful with external providers like Nerdbank.GitVersioning, ignores VERSION_FILE_PATH & VERSION_REGEX 75 | TAG_COMMIT | `true` | Flag to toggle git tagging, enabled by default 76 | TAG_FORMAT | `v*` | Format of the git tag, `[*]` gets replaced with actual version 77 | NUGET_KEY | | API key to authenticate with NuGet server 78 | NUGET_SOURCE | `https://api.nuget.org` | NuGet server uri hosting the packages, defaults to https://api.nuget.org 79 | INCLUDE_SYMBOLS | `false` | Flag to toggle pushing symbols along with nuget package to the server, disabled by default 80 | NO_BUILD | `false` | Flag to toggle not building the project and letting pack command handle restoring & building, disabled by default 81 | 82 | ## Outputs 83 | 84 | Output | Description 85 | --- | --- 86 | VERSION | Version of the associated git tag 87 | PACKAGE_NAME | Name of the NuGet package generated 88 | PACKAGE_PATH | Path to the generated NuGet package 89 | SYMBOLS_PACKAGE_NAME | Name of the symbols package generated 90 | SYMBOLS_PACKAGE_PATH | Path to the generated symbols package 91 | 92 | **FYI:** 93 | - Outputs may or may not be set depending on the action inputs or if the action failed 94 | - `NUGET_SOURCE` must support `/v3-flatcontainer/PACKAGE_NAME/index.json` for version change detection to work 95 | - Multiple projects can make use of steps to configure each project individually, common inputs between steps can be given as `env` for [job / workflow](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#env) 96 | 97 | ## License 98 | [MIT](LICENSE) 99 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: Publish NuGet@alirezanet 2 | author: Alireza Sabouri (@alirezanet) - ford Rohith Reddy (@rohith) 3 | description: Build, Pack & Publish a NuGet package with dotnet core on project version change 4 | 5 | inputs: 6 | PROJECT_FILE_PATH: 7 | description: Filepath of the project to be packaged, relative to root of repository 8 | required: true 9 | PACKAGE_NAME: 10 | description: NuGet package id, used for version detection & defaults to project name 11 | required: false 12 | VERSION_FILE_PATH: 13 | description: Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH 14 | required: false 15 | VERSION_REGEX: 16 | description: Regex pattern to extract version info in a capturing group 17 | required: false 18 | default: ^\s*(.*)<\/Version>\s*$ 19 | VERSION_STATIC: 20 | description: Useful with external providers like Nerdbank.GitVersioning, ignores VERSION_FILE_PATH & VERSION_REGEX 21 | required: false 22 | TAG_COMMIT: 23 | description: Flag to toggle git tagging, enabled by default 24 | required: false 25 | default: true 26 | TAG_FORMAT: 27 | description: Format of the git tag, [*] gets replaced with actual version 28 | required: false 29 | default: v* 30 | NUGET_KEY: 31 | description: API key to authenticate with NuGet server 32 | required: false 33 | NUGET_SOURCE: 34 | description: NuGet server uri hosting the packages, defaults to https://api.nuget.org 35 | required: false 36 | default: https://api.nuget.org 37 | INCLUDE_SYMBOLS: 38 | description: Flag to toggle pushing symbols along with nuget package to the server, disabled by default 39 | required: false 40 | default: false 41 | NO_BUILD: 42 | description: Flag to toggle not building the project and letting pack command handle restoring & building, disabled by default 43 | required: false 44 | default: false 45 | 46 | outputs: 47 | VERSION: 48 | description: Version of the associated git tag 49 | 50 | PACKAGE_NAME: 51 | description: Name of the NuGet package generated 52 | 53 | PACKAGE_PATH: 54 | description: Path to the generated NuGet package 55 | 56 | SYMBOLS_PACKAGE_NAME: 57 | description: Name of the symbols package generated 58 | 59 | SYMBOLS_PACKAGE_PATH: 60 | description: Path to the generated symbols package 61 | 62 | runs: 63 | using: node16 64 | main: index.js 65 | 66 | branding: 67 | icon: package 68 | color: blue 69 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require("os"), 2 | fs = require("fs"), 3 | path = require("path"), 4 | https = require("https"), 5 | spawnSync = require("child_process").spawnSync 6 | 7 | class Action { 8 | constructor() { 9 | this.projectFile = process.env.INPUT_PROJECT_FILE_PATH 10 | this.packageName = process.env.INPUT_PACKAGE_NAME || process.env.PACKAGE_NAME 11 | this.versionFile = process.env.INPUT_VERSION_FILE_PATH || process.env.VERSION_FILE_PATH || this.projectFile 12 | this.versionRegex = new RegExp(process.env.INPUT_VERSION_REGEX || process.env.VERSION_REGEX, "m") 13 | this.version = process.env.INPUT_VERSION_STATIC || process.env.VERSION_STATIC 14 | this.tagCommit = JSON.parse(process.env.INPUT_TAG_COMMIT || process.env.TAG_COMMIT) 15 | this.tagFormat = process.env.INPUT_TAG_FORMAT || process.env.TAG_FORMAT 16 | this.nugetKey = process.env.INPUT_NUGET_KEY || process.env.NUGET_KEY 17 | this.nugetSource = process.env.INPUT_NUGET_SOURCE || process.env.NUGET_SOURCE 18 | this.includeSymbols = JSON.parse(process.env.INPUT_INCLUDE_SYMBOLS || process.env.INCLUDE_SYMBOLS) 19 | this.noBuild = JSON.parse(process.env.INPUT_NO_BUILD || process.env.NO_BUILD) 20 | this._output = [] 21 | } 22 | 23 | _printErrorAndExit(msg) { 24 | console.log(`##[error]😭 ${msg}`) 25 | throw new Error(msg) 26 | } 27 | 28 | _setOutput(name, value) { 29 | this._output.push(`${name}=${value}`) 30 | } 31 | 32 | _flushOutput() { 33 | const filePath = process.env['GITHUB_OUTPUT'] 34 | 35 | if (filePath) { 36 | fs.appendFileSync(filePath, this._output.join(os.EOL)) 37 | } 38 | } 39 | 40 | _executeCommand(cmd, options) { 41 | console.log(`executing: [${cmd}]`) 42 | 43 | const INPUT = cmd.split(" "), TOOL = INPUT[0], ARGS = INPUT.slice(1) 44 | return spawnSync(TOOL, ARGS, options) 45 | } 46 | 47 | _executeInProcess(cmd) { 48 | this._executeCommand(cmd, { encoding: "utf-8", stdio: [process.stdin, process.stdout, process.stderr] }) 49 | } 50 | 51 | _tagCommit(version) { 52 | const TAG = this.tagFormat.replace("*", version) 53 | 54 | console.log(`✨ creating new tag ${TAG}`) 55 | 56 | this._executeInProcess(`git tag ${TAG}`) 57 | this._executeInProcess(`git push origin ${TAG}`) 58 | 59 | this._setOutput('VERSION', TAG) 60 | } 61 | 62 | _pushPackage(version, name) { 63 | console.log(`✨ found new version (${version}) of ${name}`) 64 | 65 | if (!this.nugetKey) { 66 | console.log("##[warning]😢 NUGET_KEY not given") 67 | return 68 | } 69 | 70 | console.log(`NuGet Source: ${this.nugetSource}`) 71 | 72 | fs.readdirSync(".").filter(fn => /\.s?nupkg$/.test(fn)).forEach(fn => fs.unlinkSync(fn)) 73 | 74 | if (!this.noBuild) { 75 | this._executeInProcess(`dotnet build -c Release ${this.projectFile}`) 76 | } 77 | 78 | this._executeInProcess(`dotnet pack ${this.includeSymbols ? "--include-symbols -p:SymbolPackageFormat=snupkg" : ""} -c Release ${this.projectFile} -o .`) 79 | 80 | const packages = fs.readdirSync(".").filter(fn => fn.endsWith("nupkg")) 81 | console.log(`Generated Package(s): ${packages.join(", ")}`) 82 | 83 | const pushCmd = `dotnet nuget push *.nupkg -s ${this.nugetSource}/v3/index.json -k ${this.nugetKey} --skip-duplicate${!this.includeSymbols ? " -n" : ""}`, 84 | pushOutput = this._executeCommand(pushCmd, { encoding: "utf-8" }).stdout 85 | 86 | console.log(pushOutput) 87 | 88 | if (/error/.test(pushOutput)) 89 | this._printErrorAndExit(`${/error.*/.exec(pushOutput)[0]}`) 90 | 91 | const packageFilename = packages.filter(p => p.endsWith(".nupkg"))[0], 92 | symbolsFilename = packages.filter(p => p.endsWith(".snupkg"))[0] 93 | 94 | this._setOutput('PACKAGE_NAME', packageFilename) 95 | this._setOutput('PACKAGE_PATH', path.resolve(packageFilename)) 96 | 97 | if (symbolsFilename) { 98 | this._setOutput('SYMBOLS_PACKAGE_NAME', symbolsFilename) 99 | this._setOutput('SYMBOLS_PACKAGE_PATH', path.resolve(symbolsFilename)) 100 | } 101 | 102 | if (this.tagCommit) 103 | this._tagCommit(version) 104 | } 105 | 106 | _checkForUpdate() { 107 | if (!this.packageName) { 108 | this.packageName = path.basename(this.projectFile).split(".").slice(0, -1).join(".") 109 | } 110 | 111 | console.log(`Package Name: ${this.packageName}`) 112 | 113 | let url = `${this.nugetSource}/v3-flatcontainer/${this.packageName.toLowerCase()}/index.json` 114 | console.log(`Getting versions from ${url}`) 115 | https.get(url, res => { 116 | let body = "" 117 | 118 | if (res.statusCode == 404) { 119 | console.log('404 response, assuming new package') 120 | this._pushPackage(this.version, this.packageName) 121 | } 122 | 123 | 124 | if (res.statusCode == 200) { 125 | res.setEncoding("utf8") 126 | res.on("data", chunk => body += chunk) 127 | res.on("end", () => { 128 | const existingVersions = JSON.parse(body) 129 | console.log(`Versions retrieved: ${existingVersions.versions}`) 130 | if (existingVersions.versions.indexOf(this.version) < 0) 131 | this._pushPackage(this.version, this.packageName) 132 | }) 133 | } 134 | }).on("error", e => { 135 | this._printErrorAndExit(`error: ${e.message}`) 136 | }) 137 | } 138 | 139 | run() { 140 | if (!this.projectFile || !fs.existsSync(this.projectFile)) 141 | this._printErrorAndExit("project file not found") 142 | 143 | console.log(`Project Filepath: ${this.projectFile}`) 144 | 145 | if (!this.version) { 146 | if (this.versionFile !== this.projectFile && !fs.existsSync(this.versionFile)) 147 | this._printErrorAndExit("version file not found") 148 | 149 | console.log(`Version Filepath: ${this.versionFile}`) 150 | console.log(`Version Regex: ${this.versionRegex}`) 151 | 152 | const versionFileContent = fs.readFileSync(this.versionFile, { encoding: "utf-8" }), 153 | parsedVersion = this.versionRegex.exec(versionFileContent) 154 | 155 | if (!parsedVersion) 156 | this._printErrorAndExit("unable to extract version info!") 157 | 158 | this.version = parsedVersion[1] 159 | } 160 | 161 | console.log(`Version: ${this.version}`) 162 | 163 | this._checkForUpdate() 164 | this._flushOutput() 165 | } 166 | } 167 | 168 | new Action().run() 169 | --------------------------------------------------------------------------------