Reusable GitHub workflows for repositories in the c4a8 org.
- Deploy Azure Function workflow
- Publish NuGet package workflow
- Epoch Semantic Versioning workflow
- Semantic Versioning workflow
This workflow simplifies the process of deploying Azure Functions. Use this reusable workflow at .github/workflows/deploy-azure-function.yml.
# .github/workflows/deploy-azure-function.yml in a consumer repository
name: Deploy Azure Function
on:
push:
branches:
- main
jobs:
publish:
uses: c4a8/c4a8-code-reusable-actions/.github/workflows/deploy-azure-function.yml@main
with:
dotnet_version: "10.x" # required: version of .NET SDK to use
tenant_id: "tenant-id" # required: of the tenant id
subscription_id: "subscription-id" # required: of the subscription id
client_id: "client-id" # required: of the client id of the specific function
function_app_name: "function-app-name" # required: name of the function app
environment: "production" # optional: deployment environment (e.g., 'staging', 'production')
funcignore: false # optional: respect the .funcignore file during deployment
nuget_source_name: "nuget-src" # optional: if you have configured a nuget.config
project_path: "project/project.csproj" # required: relative path to the .csproj file to build and publish
secrets:
github_pat: "${{ secrets.GITHUB_TOKEN }}" # optional: required when nuget_source_name is setdotnet_version(string, default: empty) – Version of .NET SDK to use (e.g., '10.x').tenant_id(string, default: empty) – Azure Tenant ID (GUID). Found in Azure Portal under Azure Active Directory > Overview.subscription_id(string, default: empty) – Azure Subscription ID (GUID). Found in Azure Portal under Subscriptions.client_id(string, default: empty) – Azure Client ID (GUID) of the app registration used for OIDC authentication.function_app_name(string, default: empty) – Provide the function app name.environment(string, default: production) – (Optional) Define your deployment environment (e.g., 'staging', 'production').funcignore(boolean, default: false) – (Optional) Respect the .funcignore file during deployment. A warning shows up if set to true without .funcignore file.nuget_source_name(string, default: "") – (Optional) Name of the NuGet package source (used in NuGetPackageSourceCredentials_{name}). Leave empty to skip NuGet authentication.project_path(string, default: empty) – Relative path to the .csproj file to build and publish.
Secrets
github_pat(secret) – (Optional) GitHub Personal Access Token. Pass viasecrets: github_pat: ${{ secrets.GITHUB_TOKEN }}. Required whennuget_source_nameis set.
This workflow simplifies the process of publishing NuGet packages. Use this reusable workflow at .github/workflows/publish-nuget-package-github.yml. It is designed to be used for publishing packages in the package space of a GitHub organization.
# .github/workflows/publish-nuget-package-github.yml in a consumer repository
name: Publish NuGet package
on:
push:
branches:
- main
jobs:
publish:
uses: c4a8/c4a8-code-reusable-actions/.github/workflows/publish-nuget-package-github.yml@main
with:
dotnet_version: "10.x" # required: version of .NET SDK to use
project_path: "project/project.csproj" # required: path to the .NET project file
package_output: "project/bin/Release" # required: output directory for the NuGet package
package_version: "0.0.1" # required: version of the NuGet package to publish
assembly_version: "0.0.1" # optional: set specific assembly version
secrets:
github_pat: your-pat-token # required: GitHub Personal Access Token with 'write:packages' scope - use GitHub's secret vars for thisdotnet_version(string, default: empty) – Version of .NET SDK to use (e.g., '10.x').project_path(string, default: empty) – Path to the .NET project file (e.g., 'project/project.csproj').package_output(string, default: empty) – Output directory for the NuGet package (e.g., 'project/bin/Release').package_version(string, default: empty) – Version of the NuGet package to publish (e.g., '1.0.0').assembly_version(string, default: package version) – (Optional) Set specific assembly version (e.g., '1.0.0'). If not set, it will default to package version. This version usually has the same value as the package_version.github_pat(string, default: empty) – GitHub Personal Access Token with 'write:packages' scope. Use it as secret variable ->${{ secrets.GITHUB_TOKEN }}.
This workflow generates an epoch semantic version of the form vYYYY.WW.P (e.g. v2026.8.2), where YYYY is the ISO year, WW is the ISO calendar week, and P is the patch level.
The bump type is determined from commit messages (same rules as the Semantic Versioning workflow):
- Release — triggered by
feat:,feat(<scope>):,BREAKING CHANGE:, or any type ending in!:(e.g.fix!:).- If the current calendar week/year differs from the previous stable tag: advance to the current year and week, reset patch to
0. - If the current calendar week/year is the same as the previous stable tag: increment patch (behaves like a patch bump).
- If the current calendar week/year differs from the previous stable tag: advance to the current year and week, reset patch to
- Patch — any other commit message: increment the patch level and keep the year and week of the previous stable tag, regardless of the current date.
Use the reusable workflow at
.github/workflows/epoch-semver-version.ymlto compute the next epoch semantic version, expose it as an output, and tag the current commit.
Note: This workflow is very similar to the Semantic Versioning workflow. So please fall back to that documentation for further information.
# .github/workflows/epoch-semver-version.yml in a consumer repository
name: Release
on:
push:
branches:
- main
jobs:
version:
uses: c4a8/c4a8-code-reusable-actions/.github/workflows/epoch-semver-version.yml@main
with:
prefix: license-module # optional: prefix for tags like license-module-vX.Y.Z
suppress_release: false # optional: set true to skip creating a GitHub release
suppress_tag: false # optional: set true to skip both tag and release creation
check_last_commit_only: false # optional: set true to only inspect the latest commit
is_prerelease: false # optional: set true to generate a prerelease version
prerelease_name: prerelease # optional: name for prerelease identifier (e.g., 'rc', 'alpha', 'beta')
publish:
runs-on: ubuntu-latest
needs: version
steps:
- name: Show generated version
run: |
echo "Version: ${{ needs.version.outputs.version }}"
echo "Tag: ${{ needs.version.outputs.tag }}"
echo "Bump: ${{ needs.version.outputs.bump_type }}"
echo "Prev tag:${{ needs.version.outputs.previous_tag }}"
echo "Commit: ${{ needs.version.outputs.commit_subject }}"prefix(string, default: empty) – Optional prefix prepended to generated tags (for examplelicense-module-vYYYY.WW.P).suppress_release(boolean, default: false) – Whentrue, skips creating a GitHub release while still creating tags (unless suppressed below).suppress_tag(boolean, default: false) – Whentrue, skips creating both the Git tag and the GitHub release.check_last_commit_only(boolean, default: false) – Whentrue, only the most recent commit is inspected to determine the bump type instead of all commits since the previous tag.is_prerelease(boolean, default: false) – Whentrue, generates a prerelease version (for examplev2026.8.2-rc.1).prerelease_name(string, default: "prerelease") – Name for the prerelease identifier (for examplerc,alpha,beta).
version– The calculated semantic version (for example2026.8.2).tag– The tag name that would be created (for examplev2026.8.2ormodule-v2026.8.2).bump_type– The bump classification applied (releaseorpatch).previous_tag– The most recent matching tag prior to this run, if any.commit_subject– The commit message subject that determined the bump decision.
The workflow inspects commit messages and applies the following precedence:
- Release: If any commit matches
feat:,feat(<scope>):,BREAKING CHANGE:, or any type ending in!:(for examplefeat!:,fix!:,chore!:,feat(scope)!:). - Patch: Any other commit message.
If no existing epoch-versioned tags are found, versioning starts from {currentYear}.{currentWeek}.0 (for a release bump) or {currentYear}.{currentWeek}.1 (for a patch bump).
Each run also publishes (or updates) a Git tag matching the new version. By default tags look like vYYYY.WW.P, but you can provide a prefix input (for example license-module) to emit tags such as license-module-vYYYY.WW.P. The workflow creates a GitHub release with auto-generated release notes for the generated tag. Existing tags or releases are detected and left untouched.
Use the reusable workflow at .github/workflows/semver-version.yml to compute the next semantic version, expose it as an output, and tag the current commit.
# .github/workflows/release.yml in a consumer repository
name: Release
on:
push:
branches:
- main
jobs:
version:
uses: c4a8/c4a8-code-reusable-actions/.github/workflows/semver-version.yml@main
with:
prefix: license-module # optional: prefix for tags like license-module-vX.Y.Z
suppress_release: false # optional: set true to skip creating a GitHub release
suppress_tag: false # optional: set true to skip both tag and release creation
check_last_commit_only: false # optional: set true to only inspect the latest commit
is_prerelease: false # optional: set true to generate a prerelease version
prerelease_name: prerelease # optional: name for prerelease identifier (e.g., 'rc', 'alpha', 'beta')
publish:
runs-on: ubuntu-latest
needs: version
steps:
- name: Show generated version
run: |
echo "Version: ${{ needs.version.outputs.version }}"
echo "Tag: ${{ needs.version.outputs.tag }}"
echo "Bump: ${{ needs.version.outputs.bump_type }}"
echo "Prev tag:${{ needs.version.outputs.previous_tag }}"
echo "Commit: ${{ needs.version.outputs.commit_subject }}"prefix(string, default: empty) – Optional prefix prepended to generated tags (for examplelicense-module-vX.Y.Z).suppress_release(boolean, default: false) – Whentrue, skips creating a GitHub release while still creating tags (unless suppressed below).suppress_tag(boolean, default: false) – Whentrue, skips creating both the Git tag and the GitHub release.check_last_commit_only(boolean, default: false) – Whentrue, only the most recent commit is inspected to determine the bump type instead of all commits since the previous tag.is_prerelease(boolean, default: false) – Whentrue, generates a prerelease version (for example1.2.3-rc.1).prerelease_name(string, default: "prerelease") – Name for the prerelease identifier (for examplerc,alpha,beta).
version– The calculated semantic version (for example1.2.3).tag– The tag name that would be created (for examplev1.2.3ormodule-v1.2.3).bump_type– The bump classification applied (major,minor, orpatch).previous_tag– The most recent matching tag prior to this run, if any.commit_subject– The commit message subject that determined the bump decision.
The workflow inspects the latest commit message and applies the following precedence:
- MAJOR: If the first word ends with
!:(for examplefeat!:,fix!:,chore!:,feat(scope)!:). - MINOR: If the subject starts with
feat:orfeat(<scope>):, case-insensitive. - PATCH: Any other commit message.
If no existing tags matching v* are found, versioning starts from 0.0.0.
Each run also publishes (or updates) a Git tag matching the new version. By default tags look like vX.Y.Z, but you can provide a prefix input (for example license-module) to emit tags such as license-module-vX.Y.Z. The workflow creates a GitHub release with auto-generated release notes for the generated tag. Existing tags or releases are detected and left untouched.
A PowerShell-based Git commit-msg hook is available at git/hooks/enforceConventionalCommits/commit-msg.ps1 to enforce the Conventional Commits standard locally before commits are created.
- Copy both the
commit-msg.ps1andcommit-msgto your repository's.git/hooksdirectory
- Copy just the
commit-msg.ps1to your repository's.git/hooksdirectory - Remove the .ps1 ending (so the final filename inside the
.git/hooksdirectory iscommit-msg) - Add execution permissions:
chmod +x .git/hooks/commit-msgThe hook validates that commit messages follow the Conventional Commits format:
<type>[optional scope][optional !]: <description>
[optional body]
[optional footer(s)]
| Type | Description |
|---|---|
feat |
A new feature |
fix |
A bug fix |
docs |
Documentation only changes |
style |
Code style changes (formatting, missing semicolons, etc.) |
refactor |
Code change that neither fixes a bug nor adds a feature |
perf |
Performance improvements |
test |
Adding or correcting tests |
build |
Changes to build system or dependencies |
ci |
Changes to CI configuration files and scripts |
chore |
Other changes that don't modify src or test files |
revert |
Reverts a previous commit |
deps |
Dependency updates |
feat: add user authentication
fix(api): resolve null reference exception
feat(auth)!: change login flow (breaking change)
docs: update README with setup instructions
The hook will display warnings (but not reject the commit) for:
- Long subject lines: Subject lines longer than 72 characters
- Missing breaking change documentation: When
!is used but noBREAKING CHANGE:section is present in the body