diff --git a/README.md b/README.md index c908a07..2f76431 100644 --- a/README.md +++ b/README.md @@ -12,51 +12,49 @@ on: - master # Default release branch jobs: publish: - name: list on nuget + name: build, pack & push runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - # Required for a specific dotnet version that doesn't come with ubuntu-latest / windows-latest + # Required in case of previous dotnet SDK versions as the host always has latest version installed # Visit bit.ly/2synnZl to see the list of SDKs that are pre-installed with ubuntu-latest / windows-latest # - name: Setup dotnet # uses: actions/setup-dotnet@v1 # with: # dotnet-version: 3.1.100 - + # Publish - name: publish on version change uses: rohith/publish-nuget@v2 with: PROJECT_FILE_PATH: Core/Core.csproj # Relative to repository root - # VERSION_FILE_PATH: Directory.Build.props # Filepath with version info, relative to repository root. Defaults to project file + # PACKAGE_NAME: NuGet package id, REQUIRED if it's different from project name + # VERSION_FILE_PATH: Directory.Build.props # Relative to repository root, defaults to project file # VERSION_REGEX: (.*)<\/Version> # Regex pattern to extract version info in a capturing group - # VERSION_STATIC: Bypasses version resolution; useful for external providers like Nerdbank.GitVersioning - # TAG_COMMIT: true # Flag to enable / disalge git tagging + # VERSION_STATIC: Static version, useful for external providers like Nerdbank.GitVersioning + # TAG_COMMIT: true # Flag to enable / disable git tagging # TAG_FORMAT: v* # Format of the git tag, [*] gets replaced with version - # NUGET_KEY: ${{secrets.NUGET_API_KEY}} # nuget.org API key - # PACKAGE_NAME: NuGet package name, required when it's different from project name. Defaults to project name + # NUGET_KEY: ${{secrets.NUGET_API_KEY}} # API key for the NuGet feed ``` -- With all settings on default, updates to project version are monitored on every push / PR merge to master & a new tag is created -- If a `NUGET_KEY` is present then the project gets built, packed & published to nuget.org +- Project gets built, packed & published only if there's a `NUGET_KEY` configured in the repository ## Inputs -Most of the inputs are optional Input | Default Value | Description --- | --- | --- -PROJECT_FILE_PATH | | File path of the project to be packaged, relative to repository root -VERSION_FILE_PATH | `[PROJECT_FILE_PATH]` | File path containing version info, relative to repository root +PROJECT_FILE_PATH | | Filepath of the project to be packaged, relative to root of repository +PACKAGE_NAME | | NuGet package id to check against version changes, defaults to project name +VERSION_FILE_PATH | `[PROJECT_FILE_PATH]` | Filepath containing version info, relative to root of repository VERSION_REGEX | `(.*)<\/Version>` | Regex pattern to extract version info in a capturing group -VERSION_STATIC| | Bypasses version resolution; useful for external providers like Nerdbank.GitVersioning +VERSION_STATIC| | Static version, useful for external providers like Nerdbank.GitVersioning TAG_COMMIT | `true` | Flag to enable / disable git tagging -TAG_FORMAT | `v*` | `[*]` is a placeholder for the actual project version -NUGET_KEY | | API key to authorize the package upload to nuget.org -PACKAGE_NAME | | Name of the NuGet package, required when it's different from project name +TAG_FORMAT | `v*` | Format of the git tag, `[*]` gets replaced with version +NUGET_KEY | | API key for the NuGet feed -**Note:** -For multiple projects, every input except `PROJECT_FILE_PATH` can be given as `env` variable at [job / workflow level](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/workflow-syntax-for-github-actions#env) +**Note:** +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) ## License [MIT](LICENSE) diff --git a/action.yml b/action.yml index 91dfe42..8883331 100644 --- a/action.yml +++ b/action.yml @@ -1,35 +1,35 @@ name: Publish NuGet author: Rohith Reddy (@rohith) -description: Build, Pack & Publish NuGet packages with dotnet (.net) core on project version change +description: Build, Pack & Publish a NuGet package with dotnet core on project version change inputs: PROJECT_FILE_PATH: description: Filepath of the project to be packaged, relative to root of repository required: true + PACKAGE_NAME: + description: NuGet package id to check against version changes, defaults to project name + required: false VERSION_FILE_PATH: - description: Filepath containing version info, relative to root of repository. Defaults to `PROJECT_FILE_PATH` if not specified + description: Filepath containing version info, relative to root of repository required: false VERSION_REGEX: - description: Regex (with version in a capturing group) to extract the version from `VERSION_FILE_PATH` + description: Regex pattern to extract version info in a capturing group required: false default: (.*)<\/Version> VERSION_STATIC: - description: Bypasses version resolution; useful for external providers like Nerdbank.GitVersioning + description: Static version, useful for external providers like Nerdbank.GitVersioning required: false TAG_COMMIT: - description: Whether to create a tag when there's a version change + description: Flag to enable / disable git tagging required: false default: true TAG_FORMAT: - description: Format of the tag, `*` is the placeholder for actual version + description: Format of the git tag, `[*]` gets replaced with version required: false default: v* NUGET_KEY: description: API key for the NuGet feed required: false - PACKAGE_NAME: - description: NuGet package name, required when it's different from the project name - required: false runs: using: node12 diff --git a/index.js b/index.js index 83aa502..3016045 100644 --- a/index.js +++ b/index.js @@ -5,144 +5,138 @@ const path = require("path"), class Action { constructor() { - this.PROJECT_FILE_PATH = process.env.INPUT_PROJECT_FILE_PATH - this.VERSION_FILE_PATH = process.env.INPUT_VERSION_FILE_PATH || process.env.VERSION_FILE_PATH - this.VERSION_REGEX = new RegExp(process.env.INPUT_VERSION_REGEX || process.env.VERSION_REGEX) - this.VERSION_STATIC = process.env.INPUT_VERSION_STATIC || process.env.VERSION_STATIC - this.TAG_COMMIT = JSON.parse(process.env.INPUT_TAG_COMMIT || process.env.TAG_COMMIT) - this.TAG_FORMAT = process.env.INPUT_TAG_FORMAT || process.env.TAG_FORMAT - this.NUGET_KEY = process.env.INPUT_NUGET_KEY || process.env.NUGET_KEY - this.PACKAGE_NAME = process.env.INPUT_PACKAGE_NAME || process.env.PACKAGE_NAME + this.projectFile = process.env.INPUT_PROJECT_FILE_PATH + this.versionFile = process.env.INPUT_VERSION_FILE_PATH || process.env.VERSION_FILE_PATH + this.versionRegex = new RegExp(process.env.INPUT_VERSION_REGEX || process.env.VERSION_REGEX) + this.version = process.env.INPUT_VERSION_STATIC || process.env.VERSION_STATIC + this.tagCommit = JSON.parse(process.env.INPUT_TAG_COMMIT || process.env.TAG_COMMIT) + this.tagFormat = process.env.INPUT_TAG_FORMAT || process.env.TAG_FORMAT + this.nugetKey = process.env.INPUT_NUGET_KEY || process.env.NUGET_KEY + this.packageName = process.env.INPUT_PACKAGE_NAME || process.env.PACKAGE_NAME } - _warn(msg) { + _printWarning(msg) { console.log(`##[warning]${msg}`) } - _fail(msg) { + _printErrorAndExit(msg) { console.log(`##[error]${msg}`) throw new Error(msg) } - _execCmd(cmd, options) { + _executeCommand(cmd, options) { const INPUT = cmd.split(" "), TOOL = INPUT[0], ARGS = INPUT.slice(1) return spawnSync(TOOL, ARGS, options) } - _execAndCapture(cmd) { - return this._execCmd(cmd, { encoding: "utf-8" }).stdout + _executeInProcess(cmd) { + this._executeCommand(cmd, { encoding: "utf-8", stdio: [process.stdin, process.stdout, process.stderr] }) } - _execInProc(cmd) { - this._execCmd(cmd, { encoding: "utf-8", stdio: [process.stdin, process.stdout, process.stderr] }) - } + _mayResolveFilepath(filePath, errMsg) { + const fullPath = path.resolve(process.env.GITHUB_WORKSPACE, filePath) + + if (!fs.existsSync(fullPath)) + this._printErrorAndExit(errMsg) - _resolveIfExists(filePath, errMsg) { - const FULLPATH = path.resolve(process.env.GITHUB_WORKSPACE, filePath) + return fullPath + } - if (!fs.existsSync(FULLPATH)) - this._fail(errMsg) + _tagCommit(version) { + const TAG = this.tagFormat.replace("*", version) - return FULLPATH + this._executeInProcess(`git tag ${TAG}`) + this._executeInProcess(`git push origin ${TAG}`) } _pushPackage() { - if (!this.NUGET_KEY) { - this._warn("😢 NUGET_KEY not given") + if (!this.nugetKey) { + this._printWarning("😢 NUGET_KEY not given") return } - if (!this._execAndCapture("dotnet --version")) { - this._warn("😭 dotnet not found") - return - } + fs.readdirSync(".").filter(fn => fn.endsWith("nupkg")).forEach(fn => fs.unlinkSync(fn)) - this._execInProc(`dotnet build -c Release ${this.PROJECT_FILE_PATH}`) - this._execInProc(`dotnet pack --no-build -c Release ${this.PROJECT_FILE_PATH} -o .`) + this._executeInProcess(`dotnet build -c Release ${this.projectFile}`) + this._executeInProcess(`dotnet pack --include-symbols -p:SymbolPackageFormat=snupkg --no-build -c Release ${this.projectFile} -o .`) - const generatedPackage = fs.readdirSync(".").filter(fn => fn.startsWith(this.PACKAGE_NAME) && fn.endsWith(".nupkg")).shift() - console.log(`Generated Package: ${generatedPackage}`) + const packages = fs.readdirSync(".").filter(fn => fn.endsWith("nupkg")) + console.log(`Generated Package(s): ${packages.join(", ")}`) - const NUGET_PUSH_RESPONSE = this._execAndCapture(`dotnet nuget push ${generatedPackage} -s https://api.nuget.org/v3/index.json -k ${this.NUGET_KEY}`) - const NUGET_ERROR_REGEX = /(error: Response status code does not indicate success.*)/ + const pushCmd = `dotnet nuget push *.nupkg -s https://api.nuget.org/v3/index.json -k ${this.nugetKey} --skip-duplicate`, + pushOutput = this._executeCommand(pushCmd, { encoding: "utf-8" }).stdout - console.log(NUGET_PUSH_RESPONSE) + console.log(pushOutput) - if (NUGET_ERROR_REGEX.test(NUGET_PUSH_RESPONSE)) - this._fail(`😭 ${NUGET_ERROR_REGEX.exec(NUGET_PUSH_RESPONSE)[1]}`) + if (/error/.test(pushOutput)) + this._printErrorAndExit(`😭 ${/error.*/.exec(pushOutput)[0]}`) } - _tagCommit(version) { - if (this.TAG_COMMIT) { - const TAG = this.TAG_FORMAT.replace("*", version) - - if (this._execAndCapture(`git ls-remote --tags origin ${TAG}`).indexOf(TAG) >= 0) { - this._warn(`😢 tag ${TAG} already exists`) - return - } + _mayTagAndPush(version, name) { + console.log(`👍 found a new version (${version}) of ${name}`) - this._execInProc(`git tag ${TAG}`) - this._execInProc(`git push origin ${TAG}`) - } - } + if (this.tagCommit) + this._tagCommit(version) - _pushAndTag(version, name) { - console.log(`👍 found a new version (${version}) of ${name}`) - this._tagCommit(version) this._pushPackage() } - run() { - if (!this.PROJECT_FILE_PATH) - this._fail("😭 project file not given") - - this.PROJECT_FILE_PATH = this._resolveIfExists(this.PROJECT_FILE_PATH, "😭 project file not found") + _determineProjectFilepath() { + if (!this.projectFile) + this._printErrorAndExit("😭 project file not given") - console.log(`Project Filepath: ${this.PROJECT_FILE_PATH}`) - - let CURRENT_VERSION - - if (!this.VERSION_STATIC) { - this.VERSION_FILE_PATH = !this.VERSION_FILE_PATH ? this.PROJECT_FILE_PATH : this._resolveIfExists(this.VERSION_FILE_PATH, "😭 version file not found") + this.projectFile = this._mayResolveFilepath(this.projectFile, "😭 project file not found") + console.log(`Project Filepath: ${this.projectFile}`) + } - console.log(`Version Filepath: ${this.VERSION_FILE_PATH}`) + _determineVersion() { + if (!this.version) { + this.versionFile = !this.versionFile ? this.projectFile : this._mayResolveFilepath(this.versionFile, "😭 version file not found") + console.log(`Version Filepath: ${this.versionFile}`) - const FILE_CONTENT = fs.readFileSync(this.VERSION_FILE_PATH, { encoding: "utf-8" }), - VERSION_INFO = this.VERSION_REGEX.exec(FILE_CONTENT) + const versionFileContent = fs.readFileSync(this.versionFile, { encoding: "utf-8" }), + parsedVersion = this.versionRegex.exec(versionFileContent) - if (!VERSION_INFO) - this._fail("😢 unable to extract version info!") + if (!parsedVersion) + this._printErrorAndExit("😢 unable to extract version info!") - CURRENT_VERSION = VERSION_INFO[1] - } else - CURRENT_VERSION = this.VERSION_STATIC + this.version = parsedVersion[1] + } - console.log(`Version: ${CURRENT_VERSION}`) + console.log(`Version: ${this.version}`) + } - if (!this.PACKAGE_NAME) { - this.PACKAGE_NAME = path.basename(this.PROJECT_FILE_PATH).split(".").slice(0, -1).join(".") - console.log(`Package Name: ${this.PACKAGE_NAME}`) + _checkForUpdate() { + if (!this.packageName) { + this.packageName = path.basename(this.projectFile).split(".").slice(0, -1).join(".") + console.log(`Package Name: ${this.packageName}`) } - https.get(`https://api.nuget.org/v3-flatcontainer/${this.PACKAGE_NAME}/index.json`, res => { + https.get(`https://api.nuget.org/v3-flatcontainer/${this.packageName}/index.json`, res => { let body = "" if (res.statusCode == 404) - this._pushAndTag(CURRENT_VERSION, this.PACKAGE_NAME) + this._mayTagAndPush(this.version, this.packageName) if (res.statusCode == 200) { res.setEncoding("utf8") res.on("data", chunk => body += chunk) res.on("end", () => { const existingVersions = JSON.parse(body) - if (existingVersions.versions.indexOf(CURRENT_VERSION) < 0) - this._pushAndTag(CURRENT_VERSION, this.PACKAGE_NAME) + if (existingVersions.versions.indexOf(this.version) < 0) + this._mayTagAndPush(this.version, this.packageName) }) } }).on("error", e => { - this._warn(`😢 error reaching nuget.org ${e.message}`) + this._printWarning(`😢 error reaching nuget.org ${e.message}`) }) } + + run() { + this._determineProjectFilepath() + this._determineVersion() + this._checkForUpdate() + } } new Action().run()