Skip to content
Merged
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
142 changes: 142 additions & 0 deletions .github/workflows/reusable-docker-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: Reusable Docker Build

on:
workflow_call:
inputs:
image-name:
description: Docker image name (e.g. "forgespace/mcp-gateway")
required: true
type: string
dockerfile:
description: Path to Dockerfile
required: false
type: string
default: "Dockerfile"
context:
description: Docker build context path
required: false
type: string
default: "."
platforms:
description: Target platforms (comma-separated)
required: false
type: string
default: "linux/amd64"
push:
description: Push image to registry
required: false
type: boolean
default: false
tag-strategy:
description: "Tag strategy: 'version' (from package/pyproject), 'git' (tag/sha), or 'custom'"
required: false
type: string
default: "git"
custom-tags:
description: Custom tags (newline-separated, used when tag-strategy=custom)
required: false
type: string
default: ""
build-args:
description: Docker build args (newline-separated KEY=VALUE)
required: false
type: string
default: ""
test-command:
description: Command to test the built image (empty to skip)
required: false
type: string
default: ""
secrets:
DOCKER_USERNAME:
required: false
DOCKER_PASSWORD:
required: false

permissions:
contents: read
packages: write

jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Set up QEMU
if: contains(inputs.platforms, ',')
uses: docker/setup-qemu-action@v3

- name: Login to Docker Hub
if: inputs.push && secrets.DOCKER_USERNAME != ''
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Login to GHCR
if: inputs.push
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Generate tags
id: tags
env:
IMAGE_NAME: ${{ inputs.image-name }}
TAG_STRATEGY: ${{ inputs.tag-strategy }}
CUSTOM_TAGS: ${{ inputs.custom-tags }}
GH_REF_NAME: ${{ github.ref_name }}
GH_REF_TYPE: ${{ github.ref_type }}
run: |
TAGS=""
case "$TAG_STRATEGY" in
version)
if [ -f package.json ]; then
VERSION=$(node -p "require('./package.json').version")
elif [ -f pyproject.toml ]; then
VERSION=$(grep '^version' pyproject.toml | head -1 | sed 's/.*"\(.*\)".*/\1/')
fi
TAGS="ghcr.io/$IMAGE_NAME:$VERSION"
TAGS="$TAGS,ghcr.io/$IMAGE_NAME:latest"
;;
git)
SHA=$(git rev-parse --short HEAD)
TAGS="ghcr.io/$IMAGE_NAME:$SHA"
if [ -n "$GH_REF_NAME" ] && [ "$GH_REF_TYPE" = "tag" ]; then
TAGS="$TAGS,ghcr.io/$IMAGE_NAME:${GH_REF_NAME#v}"
TAGS="$TAGS,ghcr.io/$IMAGE_NAME:latest"
fi
;;
custom)
TAGS="$CUSTOM_TAGS"
;;
esac
echo "tags=$TAGS" >> "$GITHUB_OUTPUT"

- name: Build and push
uses: docker/build-push-action@v6
with:
context: ${{ inputs.context }}
file: ${{ inputs.dockerfile }}
platforms: ${{ inputs.platforms }}
push: ${{ inputs.push }}
tags: ${{ steps.tags.outputs.tags }}
build-args: ${{ inputs.build-args }}
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Test image
if: inputs.test-command != ''
env:
IMAGE_TAG: ${{ steps.tags.outputs.tags }}
TEST_COMMAND: ${{ inputs.test-command }}
run: |
FIRST_TAG=$(echo "$IMAGE_TAG" | cut -d',' -f1)
docker run --rm "$FIRST_TAG" $TEST_COMMAND
139 changes: 139 additions & 0 deletions .github/workflows/reusable-publish-npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
name: Reusable npm Publish

on:
workflow_call:
inputs:
node-version:
description: Node.js version
required: false
type: string
default: "22"
build-command:
description: Build command (e.g. "npm run build" or "npm run build:lib")
required: false
type: string
default: "npm run build"
test-command:
description: Test command (empty to skip)
required: false
type: string
default: "npm test"
lint-command:
description: Lint command (empty to skip)
required: false
type: string
default: ""
typecheck-command:
description: Type check command (empty to skip)
required: false
type: string
default: ""
provenance:
description: Enable npm provenance attestation
required: false
type: boolean
default: true
dry-run:
description: Publish with --dry-run (no actual publish)
required: false
type: boolean
default: false
create-release:
description: Create GitHub Release after publish
required: false
type: boolean
default: true
install-command:
description: Install command
required: false
type: string
default: "npm ci"
secrets:
NPM_TOKEN:
required: true

permissions:
contents: write
id-token: write

jobs:
validate:
name: Validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
- run: ${{ inputs.install-command }}

- name: Lint
if: inputs.lint-command != ''
run: ${{ inputs.lint-command }}

- name: Type Check
if: inputs.typecheck-command != ''
run: ${{ inputs.typecheck-command }}

- name: Build
run: ${{ inputs.build-command }}

- name: Test
if: inputs.test-command != ''
run: ${{ inputs.test-command }}

- name: Security Audit
run: npm audit --audit-level high --omit dev || true

publish:
name: Publish
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
cache: npm
registry-url: "https://registry.npmjs.org"
- run: ${{ inputs.install-command }}
- run: ${{ inputs.build-command }}

- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
PROVENANCE: ${{ inputs.provenance }}
DRY_RUN: ${{ inputs.dry-run }}
run: |
FLAGS="--access public"
if [ "$PROVENANCE" = "true" ]; then
FLAGS="$FLAGS --provenance"
fi
if [ "$DRY_RUN" = "true" ]; then
FLAGS="$FLAGS --dry-run"
fi
npm publish $FLAGS

- name: Verify publish
if: inputs.dry-run == false
run: |
PACKAGE=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
sleep 5
npm view "$PACKAGE@$VERSION" version

release:
name: GitHub Release
needs: publish
if: inputs.create-release && inputs.dry-run == false
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Get version
id: version
run: echo "version=v$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT"
- uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.version }}
generate_release_notes: true