Skip to content
This repository has been archived by the owner on Oct 26, 2022. It is now read-only.

Single step multiple project-nuget Deploy. #61

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,14 @@ jobs:
id: publish_nuget
uses: rohith/publish-nuget@v2
with:
# Filepath of the solution of which contains all the projects to be packed, relative to root of repository
SOLUTION_FILE_PATH: solution.sln

# Filepath of the project to be packaged, relative to root of repository
PROJECT_FILE_PATH: Core/Core.csproj
PROJECT_FILE_PATH: **/*.csproj

# Path to store all generated nuget packages, relative to root of repository
PACKAGE_PATH: artifacts/

# NuGet package id, used for version detection & defaults to project name
# PACKAGE_NAME: Core
Expand Down Expand Up @@ -64,7 +70,9 @@ jobs:

Input | Default Value | Description
--- | --- | ---
PROJECT_FILE_PATH | | Filepath of the project to be packaged, relative to root of repository
SOLUTION_FILE_PATH | | Filepath of the solution of which contains all the projects to be packed, relative to root of repository
PROJECT_FILE_PATH | | Filepath of the project to be packaged or a glob of projects in the form of \*\*/\*.csproj, relative to root of repository
PACKAGE_PATH | | Path to store all generated nuget packages, relative to root of repository
PACKAGE_NAME | | NuGet package id, used for version detection & defaults to project name
VERSION_FILE_PATH | `[PROJECT_FILE_PATH]` | Filepath with version info, relative to root of repository & defaults to PROJECT_FILE_PATH
VERSION_REGEX | `^\s*<Version>(.*)<\/Version>\s*$` | Regex pattern to extract version info in a capturing group
Expand All @@ -88,7 +96,7 @@ SYMBOLS_PACKAGE_PATH | Path to the generated symbols package
**FYI:**
- Outputs may or may not be set depending on the action inputs or if the action failed
- `NUGET_SOURCE` must support `/v3-flatcontainer/PACKAGE_NAME/index.json` for version change detection to work
- 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)
- Multiple projects can use file globbing to package each of them up and push them.

## License
[MIT](LICENSE)
10 changes: 8 additions & 2 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ author: Rohith Reddy (@rohith)
description: Build, Pack & Publish a NuGet package with dotnet core on project version change

inputs:
SOLUTION_FILE_PATH:
description: Filepath of the solution of which contains all the projects to be packed, relative to root of repository
required: true
PROJECT_FILE_PATH:
description: Filepath of the project to be packaged, relative to root of repository
description: Filepath of the project to be packaged or a glob of projects in the form of **/*.csproj, relative to root of repository
required: true
PACKAGE_PATH:
description: Path to store all generated nuget packages, relative to root of repository
required: true
PACKAGE_NAME:
description: NuGet package id, used for version detection & defaults to project name
Expand Down Expand Up @@ -61,4 +67,4 @@ runs:

branding:
icon: package
color: blue
color: blue
77 changes: 54 additions & 23 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ const os = require("os"),
fs = require("fs"),
path = require("path"),
https = require("https"),
spawnSync = require("child_process").spawnSync
spawnSync = require("child_process").spawnSync,
globfs = require("glob-fs")(),
hasGlob = require("has-glob")

class Action {
constructor() {
this.solutionFile = process.env.SOLUTION_FILE_PATH
this.packagePath = process.env.PACKAGE_PATH
this.projectFile = process.env.INPUT_PROJECT_FILE_PATH
this.packageName = process.env.INPUT_PACKAGE_NAME || process.env.PACKAGE_NAME
this.versionFile = process.env.INPUT_VERSION_FILE_PATH || process.env.VERSION_FILE_PATH || this.projectFile
Expand Down Expand Up @@ -55,34 +59,14 @@ class Action {

console.log(`NuGet Source: ${this.nugetSource}`)

fs.readdirSync(".").filter(fn => /\.s?nupkg$/.test(fn)).forEach(fn => fs.unlinkSync(fn))

this._executeInProcess(`dotnet build -c Release ${this.projectFile}`)

this._executeInProcess(`dotnet pack ${this.includeSymbols ? "--include-symbols -p:SymbolPackageFormat=snupkg" : ""} --no-build -c Release ${this.projectFile} -o .`)

const packages = fs.readdirSync(".").filter(fn => fn.endsWith("nupkg"))
console.log(`Generated Package(s): ${packages.join(", ")}`)

const pushCmd = `dotnet nuget push *.nupkg -s ${this.nugetSource}/v3/index.json -k ${this.nugetKey} --skip-duplicate ${!this.includeSymbols ? "-n 1" : ""}`,
const pushCmd = `dotnet nuget push ${this.packagePath}/${name}.${version}.nupkg -s ${this.nugetSource}/v3/index.json -k ${this.nugetKey} --skip-duplicate ${!this.includeSymbols ? "-n 1" : ""}`,
pushOutput = this._executeCommand(pushCmd, { encoding: "utf-8" }).stdout

console.log(pushOutput)

if (/error/.test(pushOutput))
this._printErrorAndExit(`${/error.*/.exec(pushOutput)[0]}`)

const packageFilename = packages.filter(p => p.endsWith(".nupkg"))[0],
symbolsFilename = packages.filter(p => p.endsWith(".snupkg"))[0]

process.stdout.write(`::set-output name=PACKAGE_NAME::${packageFilename}` + os.EOL)
process.stdout.write(`::set-output name=PACKAGE_PATH::${path.resolve(packageFilename)}` + os.EOL)

if (symbolsFilename) {
process.stdout.write(`::set-output name=SYMBOLS_PACKAGE_NAME::${symbolsFilename}` + os.EOL)
process.stdout.write(`::set-output name=SYMBOLS_PACKAGE_PATH::${path.resolve(symbolsFilename)}` + os.EOL)
}

if (this.tagCommit)
this._tagCommit(version)
}
Expand Down Expand Up @@ -114,7 +98,8 @@ class Action {
})
}

run() {
_run_internal()
{
if (!this.projectFile || !fs.existsSync(this.projectFile))
this._printErrorAndExit("project file not found")

Expand All @@ -140,6 +125,52 @@ class Action {

this._checkForUpdate()
}

run() {
if (!this.solutionFile || !fs.existsSync(this.solutionFile)) {
this._printErrorAndExit("solution file not found")
}

if (!this.packagePath || !fs.existsSync(this.packagePath)) {
this._printErrorAndExit("PACKAGE_PATH not provided.")
}

// nuke ane normal .nupkg/.snupkg inside the user specified package path
// and then build the packages for the resulting projects inside of the
// solution file specified.
fs.readdirSync(this.packagePath).filter(fn => /\.s?nupkg$/.test(fn)).forEach(fn => fs.unlinkSync(fn))
this._executeInProcess(`dotnet build -c Release ${this.solutionFile}`)
this._executeInProcess(`dotnet pack ${this.includeSymbols ? "--include-symbols -p:SymbolPackageFormat=snupkg" : ""} --no-build --no-restore -c Release ${this.solutionFile} -o ${this.packagePath}`)
const packages = fs.readdirSync(this.packagePath).filter(fn => fn.endsWith("nupkg"))
console.log(`Generated Package(s): ${packages.join(", ")}`)

// TODO: output all of the package names and paths.
const packageFilename = packages.filter(p => p.endsWith(".nupkg"))[0],
symbolsFilename = packages.filter(p => p.endsWith(".snupkg"))[0]
process.stdout.write(`::set-output name=PACKAGE_NAME::${packageFilename}` + os.EOL)
process.stdout.write(`::set-output name=PACKAGE_PATH::${path.resolve(packageFilename)}` + os.EOL)
if (symbolsFilename) {
process.stdout.write(`::set-output name=SYMBOLS_PACKAGE_NAME::${symbolsFilename}` + os.EOL)
process.stdout.write(`::set-output name=SYMBOLS_PACKAGE_PATH::${path.resolve(symbolsFilename)}` + os.EOL)
}

if (!hasGlob(this.projectFile) && !hasGlob(this.versionFile)) {
this._run_internal()
}

// it has a glob, now we need to recursively obtain all files
// represented in the glob and match them up. After that we
// need to reset the projectFile, and versionFile variables on
// the object instance each time and call _run_internal() for
// each file found that matches in the glob.
let tmp = this.projectFile
glob.on('include', function (file) {
this.projectFile = file.relative
this.versionFile = file.relative

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was with you on every thing in the PR but this. Rather than mutating the current instance of the Action, make this a pure method, passing project and version into _run_internal as parameters.

Also, what if the version file is not the project file? What if the version is indicated in a .nuspec file? That was the intent of the versionfile property.

Copy link
Contributor Author

@AraHaan AraHaan May 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The issue with file globbing is well the following:

  • user uses file globbing for project file path ('**/*.csproj')
  • user uses file globbing for nuspec files (usually using nuspec files manually is not wanted by developers as they tend to go outdated fast and so they trick the .NET SDK to generate them instead).

For the nuspec case I think one could glob for them but then one would have to:

  • filter the include event between csproj and nuspec files.

I guess there is an easier way to process the events and process the include event if I check if versionFile specifies an glob pattern and then resolve those first, then resolve the project globs and then pass them in properly.

But this results in other issues:

  • what if some projects do not use nuspec, but some do on the same repository all of which package up packages to be published
  • what would prevent a bug where the file globbing to fix the nuspec issue ends up resulting in it not wanting to read the version that is embedded in the projects (csprojs) that do not have a nuspec file paired with it?

Because of this many projects actually define the versions in the actual project files then tell the .NET SDK to pass that version property from msbuild on over to the nuget command line where then it is used to build the package (Microsoft's own SourceGenerator and Analyzer packages do this btw).

Anyways I recommend defining the version in:

  • the project file
  • the project's version of Directory.Build.props

this._run_internal()
})
glob.readdirSync(this.projectFile);
}
}

new Action().run()