Reusable GitHub Actions workflows for Node and .NET packages. Provides separate, composable workflows for CI checks, publishing, release PR preparation, and GitHub Release creation.
Tags are updated automatically on push to main when workflow files change. Use workflow_dispatch to manually update multiple tags at once.
| Tag | Workflows |
|---|---|
node-libs-v1 |
node-lib.yml (deprecated — use v2 split workflows) |
node-libs-v2 |
node-ci.yml, node-publish.yml |
dotnet-libs-v1 |
dotnet-package.yml (deprecated — use v2 split workflows) |
dotnet-libs-v2 |
dotnet-ci.yml, dotnet-publish.yml |
release-v1 |
prepare-release.yml, create-release.yml, node-bump-main.yml |
# manual fallback — move a single tag
TAG=<TAG> && git tag -f $TAG && git push origin $TAG -fRuns lint, build, and test. No publish, no version logic. Use on PRs and pushes.
Inputs
| Input | Default | Description |
|---|---|---|
node-version-file |
package.json |
File containing the Node version spec |
package-manager |
npm |
npm or pnpm |
private-npm-registry |
— | Private registry URL; configures auth when set |
private-npm-scope |
— | Scope for private registry; auto-resolved when omitted |
Secrets private-npm-auth-token
Resolves the version via version-builder-action, bumps package.json, installs, builds, and publishes the package. Designed to run after node-ci.yml — does not repeat lint/test.
Inputs
| Input | Default | Description |
|---|---|---|
node-version-file |
package.json |
File containing the Node version spec |
package-manager |
npm |
npm or pnpm |
registry-url |
https://registry.npmjs.org |
NPM registry to publish to |
private-npm-registry |
— | Private registry URL |
private-npm-scope |
— | Scope for private registry |
preid-branches |
(action default) | Branch → preid mapping e.g. main:rc,develop:dev |
force-preid |
false |
Force preid even if branch doesn't match |
publish-command |
npm run release |
Command used to publish |
version-replace |
0.0.0-PLACEHOLDER |
Placeholder string to replace in source |
version-replace-glob |
src/version.ts |
Glob of files to replace placeholder in; "" to skip |
Secrets private-npm-auth-token
Outputs
| Output | Example | Description |
|---|---|---|
version |
2.1.0-rc.5 |
Full published version |
baseVersion |
2.1.0 |
Version without preid |
isPrerelease |
true |
Whether this is a pre-release |
tag |
rc / latest / v1-lts |
NPM dist-tag used |
majorVersion |
2 |
Major version number |
minorVersion |
1 |
Minor version number |
patchVersion |
0 |
Patch version number |
After a pre-release publish on main, force-pushes the current HEAD to a release/v{baseVersion} branch, ensures the v{major} stable branch exists (creates it automatically on first use), and always creates (or updates) a PR from release/v{baseVersion} → v{major}. On first bootstrap, v{major} is created one commit behind the release branch so the PR has a real file diff — ensuring a squash merge onto v{major} always contains real changes and correctly triggers push-based workflows. Language-agnostic.
Inputs
| Input | Required | Description |
|---|---|---|
base-version |
✅ | e.g. 2.1.0 |
title |
— | PR title override; defaults to release: v{base-version} |
Secrets
| Secret | Required | Description |
|---|---|---|
token |
— | GitHub token to use. Defaults to GITHUB_TOKEN. Public repos must supply a PAT or GitHub App token with the workflow scope — GITHUB_TOKEN cannot push branches containing .github/workflows/ files on public repos. |
Creates the exact git tag (v2.1.0), force-updates the floating major tag (v2), and publishes a GitHub Release with auto-generated notes. Automatically determines whether to mark the release as --latest by comparing the major version against all existing tags. Language-agnostic — used by both Node and .NET publish flows.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
version |
✅ | — | e.g. 2.1.0 |
tag-tmpl |
— | v{major} |
Tag template; {major} is replaced with the major version number. e.g. v{major} → v2, {major}.x → 2.x |
Outputs
| Output | Example | Description |
|---|---|---|
is-latest |
true |
Whether this major is the highest released. Used to guard bump-main. |
After a stable release on the latest major, bumps the minor version in package.json on the default branch and opens a PR. Uses npm version minor --no-git-tag-version and commits with [skip ci] to avoid redundant CI runs on the bump branch and PR. Callers should guard with create-release output is-latest == 'true' so backport releases (e.g. v1.x while main is on v2) don't trigger a spurious bump.
Inputs
| Input | Required | Default | Description |
|---|---|---|---|
released-version |
✅ | — | The just-released stable version e.g. 2.1.0 |
default-branch |
— | main |
Branch to bump and target with the PR e.g. master |
Runs dotnet restore, dotnet build, and dotnet test. No publish.
Inputs
| Input | Default | Description |
|---|---|---|
dotnet-version |
10.0.x |
.NET SDK version |
dotnet-cfg |
Release |
Build configuration e.g. Release, Debug. |
solution-file |
— | Solution or project file to build. When omitted, auto-resolved from package.json#dotnetBuildSln, then blank. |
private-nuget-env-prefix |
— | Env var prefix for NuGet credentials (must match NuGet.Config %{PREFIX}_USERNAME% / %{PREFIX}_TOKEN%). When set, configures credentials. |
Secrets
| Secret | Description |
|---|---|
nuget-auth-token |
Auth token for the private NuGet registry. Also used as {PREFIX}_TOKEN for private restore credentials. Defaults to GITHUB_TOKEN. |
private-nuget-username |
Username for private NuGet registry. Defaults to github.actor. |
Resolves the version via version-builder-action, builds, packs, and pushes NuGet packages.
Inputs
| Input | Default | Description |
|---|---|---|
dotnet-version |
10.0.x |
.NET SDK version |
dotnet-cfg |
Release |
Build configuration e.g. Release, Debug. |
source-url |
https://api.nuget.org/v3/index.json |
NuGet source URL passed to setup-dotnet for credential configuration. |
source-name |
— | NuGet source name (from NuGet.Config) used for dotnet nuget push -s. Falls back to source-url when omitted. |
solution-file |
— | Solution or project file to build. When omitted, auto-resolved from package.json#dotnetBuildSln, then blank. |
private-nuget-env-prefix |
— | Env var prefix for NuGet credentials (must match NuGet.Config %{PREFIX}_USERNAME% / %{PREFIX}_TOKEN%). When set, configures credentials. |
preid-branches |
(action default) | Branch → preid mapping e.g. main:rc,develop:dev |
force-preid |
false |
Force preid even if branch doesn't match |
Secrets
| Secret | Description |
|---|---|
nuget-auth-token |
Auth token for the NuGet publish source. Also used as {PREFIX}_TOKEN for private restore credentials. Defaults to GITHUB_TOKEN. |
private-nuget-username |
Username for private NuGet registry. Defaults to github.actor. |
Outputs version, baseVersion, isPrerelease, tag, majorVersion
flowchart TD
PR["Pull Request → main"] -->|on: pull_request| CI
PUSH_MAIN["Push to main"] -->|on: push| CI
PUSH_MAIN --> CD_publish
CD_publish["CD: publish job<br/>node-publish.yml<br/>→ publishes 2.1.0-rc.5 --tag rc"]
CD_publish -->|"ref_name == 'main'"| PrepareRelease
PrepareRelease["CD: prepare-release job<br/>prepare-release.yml<br/>→ ensures v2 branch exists<br/>→ creates/updates PR<br/>release/v2.1.0 → v2"]
RELEASE_PR["Release PR merged<br/>(release/v2.1.0 → v2)"] -->|on: push to v2| CI2
RELEASE_PR --> CD_stable
CI["CI workflow<br/>node-ci.yml<br/>→ lint, build, test"]
CI2["CI workflow<br/>node-ci.yml<br/>→ lint, build, test"]
CD_stable["CD: publish job<br/>node-publish.yml<br/>→ publishes 2.1.0 --tag latest"]
CD_stable -->|"isPrerelease == false"| CreateRelease
CreateRelease["CD: release job<br/>create-release.yml<br/>→ tag v2.1.0<br/>→ float tag v2<br/>→ GitHub Release ✨<br/>outputs: is-latest"]
CreateRelease -->|"is-latest == true"| BumpMain
BumpMain["CD: bump-main job<br/>node-bump-main.yml<br/>→ bumps main to 2.2.0<br/>→ opens chore/bump-v2.2.0 PR 🔼"]
PUSH_LTS["Push to v1 (LTS fix)"] -->|on: push| CI3
PUSH_LTS --> CD_lts
CI3["CI workflow<br/>node-ci.yml<br/>→ lint, build, test"]
CD_lts["CD: publish job<br/>node-publish.yml<br/>→ publishes 1.5.3 --tag v1-lts"]
CD_lts -->|"isPrerelease == false"| CreateRelease2
CreateRelease2["CD: release job<br/>create-release.yml<br/>→ tag v1.5.3<br/>→ float tag v1<br/>→ GitHub Release (non-latest) ✨<br/>is-latest == false → no bump"]
style PrepareRelease fill:#bfdbfe,stroke:#60a5fa,color:#1e3a5f
style CreateRelease fill:#bbf7d0,stroke:#4ade80,color:#14532d
style CreateRelease2 fill:#bbf7d0,stroke:#4ade80,color:#14532d
style BumpMain fill:#ddd6fe,stroke:#a78bfa,color:#2e1065
style CD_publish fill:#fef08a,stroke:#facc15,color:#713f12
style CD_stable fill:#fef08a,stroke:#facc15,color:#713f12
style CD_lts fill:#fef08a,stroke:#facc15,color:#713f12
Minimal setup for a Node library published to a private registry using pnpm.
Public repo? The
prepare-releasejob must pass a PAT or GitHub App token with theworkflowscope viasecrets: token. Store it as a repository secret (e.g.GH_PAT) and add to the job:prepare-release: ... secrets: token: ${{ secrets.GH_PAT }}Private repos work with the default
GITHUB_TOKENand no extra configuration.
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, "v*", "workflow"]
paths-ignore: ["**.md"]
pull_request:
branches: [main, "v*"]
paths-ignore: ["**.md"]
permissions:
contents: read
jobs:
ci:
name: node CI
uses: sketch7/.github/.github/workflows/node-ci.yml@node-libs-v2
with:
package-manager: pnpm
private-npm-registry: ${{ vars.MY_NPM_REGISTRY }}
secrets:
private-npm-auth-token: ${{ secrets.MY_NPM_TOKEN }}.github/workflows/cd.yml
name: CD
on:
push:
branches: [main, "v*", "workflow"]
paths-ignore: ["**.md"]
workflow_dispatch:
inputs:
publish:
description: "Publish 🚀"
type: boolean
default: false
force-prerelease:
description: "Force Pre-release"
type: boolean
default: true
permissions:
id-token: write
contents: write
packages: write
pull-requests: write
jobs:
publish:
name: Publish
if: |
contains(fromJSON('["main", "workflow"]'), github.ref_name) ||
startsWith(github.ref_name, 'v') ||
github.event.inputs.publish == 'true'
uses: sketch7/.github/.github/workflows/node-publish.yml@node-libs-v2
with:
package-manager: pnpm
private-npm-registry: ${{ vars.MY_NPM_REGISTRY }}
force-preid: ${{ github.event.inputs.force-prerelease == 'true' }}
version-replace-glob: "" # set to "src/version.ts" if you embed the version
secrets:
private-npm-auth-token: ${{ secrets.MY_NPM_TOKEN }}
prepare-release:
name: Prepare Release
needs: publish
if: |
needs.publish.result == 'success' &&
github.event_name == 'push' &&
github.ref_name == 'main'
uses: sketch7/.github/.github/workflows/prepare-release.yml@release-v1
with:
base-version: ${{ needs.publish.outputs.baseVersion }}
release:
name: Release
needs: publish
if: |
needs.publish.result == 'success' &&
!fromJSON(needs.publish.outputs.isPrerelease)
uses: sketch7/.github/.github/workflows/create-release.yml@release-v1
with:
version: ${{ needs.publish.outputs.version }}
bump-main:
name: Bump main
needs: [publish, release]
if: |
needs.release.result == 'success' &&
needs.release.outputs.is-latest == 'true' &&
github.event_name == 'push'
uses: sketch7/.github/.github/workflows/node-bump-main.yml@release-v1
with:
released-version: ${{ needs.publish.outputs.version }}Minimal setup for a .NET library published to NuGet.org. For a private registry (e.g. GitHub Packages), set
private-nuget-env-prefixand supply the matching credentials — see the private registry example below.
.github/workflows/ci.yml
name: CI
on:
push:
branches: [main, "v*", "workflow"]
paths-ignore: ["**.md"]
pull_request:
branches: [main, "v*"]
paths-ignore: ["**.md"]
permissions:
contents: read
packages: read
jobs:
ci:
name: dotnet CI
uses: sketch7/.github/.github/workflows/dotnet-ci.yml@dotnet-libs-v2With a private NuGet registry (e.g. GitHub Packages), add:
with: private-nuget-env-prefix: MY_NUGET source-name: my-nuget-source # source key in NuGet.Config secrets: nuget-auth-token: ${{ secrets.GITHUB_TOKEN }}And in your
NuGet.Configreference the env vars as%MY_NUGET_USERNAME%/%MY_NUGET_TOKEN%.
.github/workflows/cd.yml
name: CD
on:
push:
branches: [main, "v*", "workflow"]
paths-ignore: ["**.md"]
workflow_dispatch:
inputs:
publish:
description: "Publish 🚀"
type: boolean
default: false
force-prerelease:
description: "Force Pre-release"
type: boolean
default: true
permissions:
id-token: write
contents: write
packages: write
jobs:
publish:
name: Publish
if: |
contains(fromJSON('["main", "workflow"]'), github.ref_name) ||
startsWith(github.ref_name, 'v') ||
github.event.inputs.publish == 'true'
uses: sketch7/.github/.github/workflows/dotnet-publish.yml@dotnet-libs-v2
with:
force-preid: ${{ github.event.inputs.force-prerelease == 'true' }}
secrets:
nuget-auth-token: ${{ secrets.NUGET_TOKEN }}
prepare-release:
name: Prepare Release
needs: publish
if: |
needs.publish.result == 'success' &&
github.event_name == 'push' &&
github.ref_name == 'main'
uses: sketch7/.github/.github/workflows/prepare-release.yml@release-v1
with:
base-version: ${{ needs.publish.outputs.baseVersion }}
release:
name: Release
needs: publish
if: |
needs.publish.result == 'success' &&
!fromJSON(needs.publish.outputs.isPrerelease)
uses: sketch7/.github/.github/workflows/create-release.yml@release-v1
with:
version: ${{ needs.publish.outputs.version }}| Workflow | Replaced by |
|---|---|
node-lib.yml @node-libs-v1 |
node-ci.yml + node-publish.yml + prepare-release.yml + create-release.yml @node-libs-v2 / @release-v1 |
dotnet-package.yml @dotnet-libs-v1 |
dotnet-ci.yml + dotnet-publish.yml + create-release.yml @dotnet-libs-v2 / @release-v1 |