This repository contains reusable GitHub Actions workflows and composite actions that can be used across multiple projects to centralize CI/CD logic.
.github/
├── workflows/ # Reusable workflows
│ ├── web-build.yml # Web application build workflow
│ ├── api-build.yml # API build workflow
│ ├── node-release.yml # Node.js release workflow with semantic-release
│ ├── release-train.yml # Automatic alpha/beta/stable release train
│ ├── docker-release.yml # Version bump + Docker build & publish
│ └── test.yml # CI for this repository (detect tests + shellcheck)
├── actions/ # Composite actions
│ ├── setup/ # Common setup (Node.js, pnpm, checkout)
│ ├── install/ # Install dependencies with pnpm
│ └── release-train-detect/ # Compute the exact next version from git tags
└── tests/ # Test suites for the scripts in this repo
-
Repository Access: The target project must have access to the
sisques-labs/workflowsrepository. If it's a private repository, ensure proper permissions are configured. -
Project Structure: Your project should use:
pnpmas package manager- A monorepo structure (optional, but recommended for
app_pathusage)
To use a reusable workflow in another project, create a workflow file in your project's .github/workflows/ directory and reference the workflow using the uses keyword.
Example: Using the Web Build workflow
Create .github/workflows/ci.yml in your project:
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]
jobs:
build-web:
uses: sisques-labs/workflows/.github/workflows/web-build.yml@main
with:
app_path: "apps/web"
app_name: "My Web App"
node_version: "24"
run_lint: true
run_test: true
build_command: "build"
secrets: inherit # Required if the workflow needs secretsKey Points:
- Use
uses: sisques-labs/workflows/.github/workflows/web-build.yml@mainto reference the workflow - Replace
@mainwith the branch/tag you want to use (e.g.,@v1.0.0for versioned releases) - All inputs are passed via the
with:section - Use
secrets: inheritto pass secrets from your repository to the reusable workflow
You can also use the composite actions directly in your own workflows:
Example: Using Setup and Install actions
name: Custom Workflow
on:
push:
branches: [main]
jobs:
custom-job:
runs-on: ubuntu-latest
steps:
- name: Setup
uses: sisques-labs/workflows/.github/actions/setup@main
with:
node_version: "24"
# pnpm_version is optional - will auto-detect from package.json if not specified
- name: Install dependencies
uses: sisques-labs/workflows/.github/actions/install@main
with:
app_path: "apps/web"
use_filter: "true"
frozen_lockfile: "true"
- name: Your custom step
run: echo "Do something custom here"@main: Use for latest/development version (may have breaking changes)@v1.0.0: Use for stable, versioned releases (recommended for production)@feature-branch: Use for testing new features before merging
Recommendation: Pin to a specific version tag for production projects to ensure stability.
If you have multiple apps in a monorepo, you can create separate jobs for each:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-web:
uses: sisques-labs/workflows/.github/workflows/web-build.yml@main
with:
app_path: "apps/web"
app_name: "Web App"
build-admin:
uses: sisques-labs/workflows/.github/workflows/web-build.yml@main
with:
app_path: "apps/admin"
app_name: "Admin App"
build-api:
uses: sisques-labs/workflows/.github/workflows/api-build.yml@main
with:
app_path: "apps/api"
app_name: "API"Builds a Next.js or web application with optional linting and testing.
Usage:
name: Web Build
on:
pull_request:
paths:
- "apps/web/**"
branches: [main, dev]
jobs:
build:
uses: sisques-labs/workflows/.github/workflows/web-build.yml@main
with:
app_path: "apps/web"
app_name: "Web App"
node_version: "24"
run_lint: true
run_test: true
build_command: "build"Inputs:
app_path(required): Path to the web app (e.g.,apps/web)app_name(optional, default:"Web App"): Name of the app for displaynode_version(optional, default:"24"): Node.js version to userun_lint(optional, default:true): Whether to run lintrun_test(optional, default:true): Whether to run testsbuild_command(optional, default:"build"): Build command to run (e.g.,build,build:prod)use_filter(optional, default:false): Whether to use filter for installation
Builds a NestJS or API application with optional linting and testing.
Usage:
name: API Build
on:
pull_request:
paths:
- "apps/api/**"
branches: [main, dev]
jobs:
build:
uses: sisques-labs/workflows/.github/workflows/api-build.yml@main
with:
app_path: "apps/api"
app_name: "API"
node_version: "24"
run_lint: true
run_test: true
build_command: "build"Inputs:
app_path(required): Path to the API app (e.g.,apps/api)app_name(optional, default:"API"): Name of the app for displaynode_version(optional, default:"24"): Node.js version to userun_lint(optional, default:true): Whether to run lintrun_test(optional, default:true): Whether to run testsbuild_command(optional, default:"build"): Build command to run (e.g.,build,build:prod)use_filter(optional, default:false): Whether to use filter for installation
Automatically releases a Node.js package using semantic-release. Updates the version in package.json, creates Git tags, generates GitHub releases, and creates changelogs automatically based on conventional commits.
Usage:
name: Release
on:
push:
branches:
- main
jobs:
release:
uses: sisques-labs/workflows/.github/workflows/node-release.yml@main
secrets: inherit
with:
app_path: "packages/sdk"
build_command: "build"
use_filter: trueInputs:
app_path(optional, default:"."): Path to the app/package (e.g.,packages/sdk,apps/api). Use"."for rootworking_directory(optional): Working directory for semantic-release (defaults toapp_path)node_version(optional, default:"24"): Node.js version to usepnpm_version(optional, default:""): pnpm version to use. If empty, will auto-detect frompackage.jsonuse_filter(optional, default:false): Whether to use filter for installationbuild_command(optional): Build command to run before release (e.g.,build,build:prod)release_command(optional): Custom release command. Defaults topnpm releaseif found in package.json, otherwise usesnpx semantic-release
Requirements:
- Your project must have
semantic-releaseconfigured. You can either:- Add a
releasescript to yourpackage.json:"release": "semantic-release" - Or install
semantic-releaseas a dependency (the workflow will usenpx semantic-release)
- Add a
- The workflow requires
GITHUB_TOKEN(automatically provided) and optionallyNPM_TOKENif publishing to npm - Ensure your commits follow Conventional Commits format for automatic versioning
Example with monorepo:
name: Release SDK
on:
push:
paths:
- "packages/sdk/**"
branches:
- main
jobs:
release:
uses: sisques-labs/workflows/.github/workflows/node-release.yml@main
secrets: inherit
with:
app_path: "packages/sdk"
working_directory: "packages/sdk"
build_command: "build"
use_filter: trueFully automatic semver pipeline driven by branch merges. Every push to a train branch publishes a Docker image, a git tag, and a GitHub Release for the corresponding channel:
| Branch | Channel | Version produced | Docker tags |
|---|---|---|---|
develop |
alpha | X.Y.Z-alpha.N |
:X.Y.Z-alpha.N, :alpha |
staging |
beta | X.Y.Z-beta.N |
:X.Y.Z-beta.N, :beta |
main |
stable | X.Y.Z |
:X.Y.Z, :latest |
How versions are computed
Git tags are the single source of truth — package.json is overwritten with
the computed version at release time and is never read to derive one. The
release-train-detect action computes the exact next version in one place:
developcontinues the open alpha cycle (0.16.0-alpha.3→0.16.0-alpha.4) or opens a new one from the latest stable when the previous cycle graduated. The bump for a new cycle follows conventional commits:feat:→ minor, breaking change (!orBREAKING CHANGE:) → major, anything else → patch.stagingpromotes the newest alpha cycle tobeta.0, or iterates the current beta cycle when a fix is merged into staging directly.maingraduates the leading beta cycle to stable, or cuts a patch release for hotfixes merged straight to main.
A computed version is always strictly greater than the latest stable
release: a stale pre-release cycle whose base already shipped (e.g.
0.15.1-beta.N after v0.15.1 went stable) is abandoned, never continued.
Duplicate tags are rejected before anything is published.
Publish ordering (atomic releases)
- Lint, test, and build the Docker image without pushing.
- Commit the version bump + changelog, tag the release commit, and push both atomically. If this fails, nothing has been published anywhere.
- Push the image (instant — reuses the buildx cache) and create the GitHub Release.
This guarantees a git tag can never lag behind a published image, which is what previously allowed version reuse.
Release notes: stable releases accumulate everything shipped since the previous stable release into one flat section (the work that flowed through alpha/beta), so the notes describe what actually lands in production. Pre-release notes only cover what is new since the last tag.
Usage (consumer repository):
name: Release Train
on:
push:
branches: [develop, staging, main]
permissions:
contents: write
packages: write
# One group for the whole repo: develop/staging/main releases are serialized
# so two channels never read/write tags concurrently.
concurrency:
group: release-train
cancel-in-progress: false
jobs:
release:
uses: sisques-labs/workflows/.github/workflows/release-train.yml@main
with:
image_name: sisqueslabs/my-app
ghcr_image_name: ghcr.io/sisques-labs/my-app
push_ghcr: true
node_version: "22"
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
permissions:
contents: write
packages: writeTesting: the version-computation logic is covered by
tests/release-train-detect.test.sh, which runs on every PR to this
repository (including a regression test for the stale-beta bug).
Common setup action for repository checkout, Node.js, and pnpm installation.
Usage:
# Auto-detect pnpm version from package.json (recommended)
- name: Setup
uses: sisques-labs/workflows/.github/actions/setup@main
with:
node_version: "24"
# Or specify pnpm version explicitly
- name: Setup
uses: sisques-labs/workflows/.github/actions/setup@main
with:
node_version: "24"
pnpm_version: "9.0.0"Inputs:
node_version(optional, default:"24"): Node.js version to usepnpm_version(optional, default:""): pnpm version to use. If empty, will auto-detect frompackage.jsonpackageManagerfield
Install dependencies using pnpm with optional filter and frozen lockfile handling.
Usage:
- name: Install dependencies
uses: sisques-labs/workflows/.github/actions/install@main
with:
app_path: "apps/web"
use_filter: "true"
frozen_lockfile: "true"Inputs:
app_path(optional, default:"."): Path to the app/package (e.g.,apps/web). Use"."for rootuse_filter(optional, default:"false"): Whether to use filter for installationfrozen_lockfile(optional, default:"true"): Whether to use --frozen-lockfile (automatically skipped for dependabot)
- Always use
secrets: inheritwhen calling workflows that require secrets - Use consistent Node.js versions across your project (default is
24) - Use install_filter when you only need dependencies for a specific app/package
- Combine workflows in your project's workflow files for complete CI/CD pipelines
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build-web:
uses: sisques-labs/workflows/.github/workflows/web-build.yml@main
with:
app_path: "apps/web"
app_name: "Web App"
node_version: "24"
run_lint: true
run_test: true
build_command: "build"
build-api:
uses: sisques-labs/workflows/.github/workflows/api-build.yml@main
with:
app_path: "apps/api"
app_name: "API"
node_version: "24"
run_lint: true
run_test: true
build_command: "build"
release:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: [build-web, build-api]
uses: sisques-labs/workflows/.github/workflows/node-release.yml@main
secrets: inherit
with:
app_path: "."
build_command: "build"This is a centralized repository for reusable workflows. When adding new workflows or actions:
- Follow the existing structure and naming conventions
- Use the composite actions (
setupandinstall) when possible - Document all inputs and their defaults
- Update this README with usage examples