
Github Actions Generator
- 403 installs
- 286 repo stars
- Updated July 26, 2026
- akin-ozer/cc-devops-skills
github-actions-generator is an agent skill that scaffolds production-ready GitHub Actions workflows, reusable workflows, and custom action.yml files for developers implementing CI/CD automation.
About
github-actions-generator is a cc-devops-skills agent skill that creates .github/workflows CI/CD YAML, workflow_call reusable pipelines, and custom action.yml packages following current security and naming conventions. A trigger decision tree routes requests to workflow generation, custom action scaffolding, reusable workflow templates, or security-scanning patterns covering dependency review, SBOM, and CodeQL. The skill ships six reference guides—best practices, common actions, expressions and contexts, advanced triggers, custom actions, and modern features—and mandates post-generation validation via devops-skills:github-actions-validator with fix-and-revalidate loops. Developers install it from akin-ozer/cc-devops-skills alongside validators for Azure Pipelines, GitLab CI, Jenkins, and Terraform. Reach for it when bootstrapping GitHub CI instead of hand-writing pinned action SHAs, minimal permissions, concurrency controls, and timeout defaults from scratch.
- Workflow YAML scaffolding
- Build and test job templates
- Matrix and caching patterns
- Deploy and release stage stubs
Github Actions Generator by the numbers
- 403 all-time installs (skills.sh)
- Ranked #298 of 1,435 DevOps & CI/CD skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/akin-ozer/cc-devops-skills --skill github-actions-generatorAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 403 |
|---|---|
| repo stars | ★ 286 |
| Last updated | July 26, 2026 |
| Repository | akin-ozer/cc-devops-skills ↗ |
How do you scaffold secure GitHub Actions CI/CD workflows?
Generate GitHub Actions workflow YAML for build, test, lint, deploy, and release pipelines tailored to a repository stack.
Who is it for?
Developers bootstrapping GitHub Actions pipelines who want pinned actions, security baselines, and automatic validator checks before committing workflow YAML.
Skip if: Teams standardized on GitLab CI or Azure Pipelines only—use sibling gitlab-ci-generator or azure-pipelines-generator skills instead.
When should I use this skill?
The user asks to create, scaffold, or generate GitHub Actions workflows, action.yml, or .github/workflows CI/CD pipelines.
What you get
.github/workflows YAML files, action.yml definitions, reusable workflow templates, and validator-passing CI configurations.
- workflow YAML files
- action.yml
- reusable workflow templates
By the numbers
- Includes 6 reference guides for workflows and custom actions
- Pairs with github-actions-validator for mandatory post-generation checks
Files
GitHub Actions Generator
Generate production-ready GitHub Actions workflows and custom actions following current best practices, security standards, and naming conventions. All generated resources are automatically validated using the devops-skills:github-actions-validator skill.
Quick Reference
| Capability | When to Use | Reference |
|---|---|---|
| Workflows | CI/CD, automation, testing | references/best-practices.md |
| Composite Actions | Reusable step combinations | references/custom-actions.md |
| Docker Actions | Custom environments/tools | references/custom-actions.md |
| JavaScript Actions | API interactions, complex logic | references/custom-actions.md |
| Reusable Workflows | Shared patterns across repos | references/advanced-triggers.md |
| Security Scanning | Dependency review, SBOM | references/best-practices.md |
| Modern Features | Summaries, environments | references/modern-features.md |
---
Trigger Decision Tree
Route every request through this decision tree before reading references or generating files:
1. If the user asks for .github/workflows/*.yml CI/CD automation, choose Workflow Generation. 2. If the user asks for action.yml or a reusable step package, choose Custom Action Generation. 3. If the user asks for workflow_call or shared pipelines across repositories, choose Reusable Workflow Generation. 4. If the request includes security-only scanning (dependency review, SBOM, CodeQL), stay on Workflow Generation with the security pattern. 5. If intent is ambiguous, ask one disambiguation question: "Do you want a workflow, a custom action, or a reusable workflow?"
Progressive Disclosure Route
Load only what is needed for the selected route, in this order:
| Route | Load First (required) | Load Next (only if needed) | Primary Template |
|---|---|---|---|
| Workflow Generation | references/best-practices.md | references/common-actions.md, references/expressions-and-contexts.md, references/modern-features.md | assets/templates/workflow/basic_workflow.yml |
| Custom Action Generation | references/custom-actions.md | references/best-practices.md | assets/templates/action/composite/action.yml, assets/templates/action/docker/, assets/templates/action/javascript/ |
| Reusable Workflow Generation | references/advanced-triggers.md | references/best-practices.md, references/common-actions.md | assets/templates/workflow/reusable_workflow.yml |
If a required reference/template is unavailable, continue with the closest available reference and report the fallback explicitly in output.
---
Core Capabilities
1. Generate Workflows
Triggers: "Create a workflow for...", "Build a CI/CD pipeline..."
Process: 1. Understand requirements (triggers, runners, dependencies) 2. Define trust boundaries (internal branches vs fork PRs vs external triggers) 3. Set default permissions to read-only, then elevate only per job when required 4. Reference references/best-practices.md for patterns 5. Reference references/common-actions.md for action versions 6. Generate workflow with:
- Semantic names, pinned actions (SHA), explicit permissions
- Concurrency controls, caching, matrix strategies
- Fork-safe PR handling (no secrets in untrusted contexts)
7. Validate with devops-skills:github-actions-validator skill 8. Fix issues and re-validate if needed
Minimal Example:
name: CI Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
cache: 'npm'
- run: npm ci
- run: npm testUntrusted PR Guardrail (required for secret-using jobs):
jobs:
deploy:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository2. Generate Custom Actions
Triggers: "Create a composite action...", "Build a Docker action...", "Create a JavaScript action..."
Types:
- Composite: Combine multiple steps → Fast startup
- Docker: Custom environment/tools → Isolated
- JavaScript: API access, complex logic → Fastest
Process: 1. Use templates from assets/templates/action/ 2. Follow structure in references/custom-actions.md 3. Include branding, inputs/outputs, documentation 4. Validate with devops-skills:github-actions-validator skill
See references/custom-actions.md for:
- Action metadata and branding
- Directory structure patterns
- Versioning and release workflows
3. Generate Reusable Workflows
Triggers: "Create a reusable workflow...", "Make this workflow callable..."
Key Elements:
workflow_calltrigger with typed inputs- Explicit secrets (avoid
secrets: inherit) - Explicit trusted-caller expectations (document org/repo boundaries)
- Outputs mapped from job outputs
- Minimal permissions
on:
workflow_call:
inputs:
environment:
required: true
type: string
secrets:
deploy-token:
required: false
outputs:
result:
value: ${{ jobs.build.outputs.result }}When secrets are required, pass only the exact secret names needed and prefer environment protection rules for deployment stages.
See references/advanced-triggers.md for complete patterns.
4. Generate Security Workflows
Triggers: "Add security scanning...", "Add dependency review...", "Generate SBOM..."
Components:
- Dependency Review:
actions/dependency-review-action@v4 - SBOM Attestations:
actions/attest-sbom@v2 - CodeQL Analysis:
github/codeql-action
Permission Model: Use a read-only workflow-level baseline, then elevate only in the security job that requires write scopes.
permissions:
contents: read
jobs:
security-scan:
permissions:
contents: read
security-events: write # For CodeQL
id-token: write # For attestations
attestations: write # For attestationsSee references/best-practices.md section on security.
5. Modern Features
Triggers: "Add job summaries...", "Use environments...", "Run in container..."
See references/modern-features.md for:
- Job summaries (
$GITHUB_STEP_SUMMARY) - Deployment environments with approvals
- Container jobs with services
- Workflow annotations
6. Third-Party Action Documentation and Citation
When using third-party actions (any uses: entry not in the same repository):
1. Search for documentation:
"[owner/repo] [version] github action documentation"2. Or use Context7 MCP:
mcp__context7__resolve-library-idto find actionmcp__context7__query-docsfor documentation
3. Pin to SHA with version comment:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.24. Cite source and version in the response:
- Action source (repository URL)
- Version source (release/tag/changelog URL)
- Selected commit SHA and human-readable version
- Access date for the source used
See references/common-actions.md for pre-verified action versions.
---
Validation Workflow
CRITICAL: Every generated resource MUST be validated.
1. Generate workflow/action file 2. Invoke devops-skills:github-actions-validator skill 3. If errors: fix and re-validate 4. If success: present with usage instructions
Skip validation only for:
- Partial code snippets
- Documentation examples
- User explicitly requests skip
Fallback Behavior (Tooling and Environment Constraints)
If required tooling or network access is unavailable, use this deterministic fallback order:
1. If devops-skills:github-actions-validator is unavailable, run local fallback checks:
actionlint(if installed)yamllint(if installed)- manual YAML/schema review with a clear "not tool-validated" note
2. If Context7 or internet access is unavailable:
- use
references/common-actions.mdfor known action versions - state that external version verification could not be completed
3. If a template path is missing:
- generate from the closest template pattern in
assets/templates/ - document which template was substituted
Fallback usage must always be reported in the final output.
---
Mandatory Standards
All generated resources must follow:
| Standard | Implementation |
|---|---|
| Security | Pin to SHA, minimal permissions, mask secrets |
| Performance | Caching, concurrency, shallow checkout |
| Naming | Descriptive names, lowercase-hyphen files |
| Error Handling | Timeouts, cleanup with if: always() |
See references/best-practices.md for complete guidelines.
---
Resources
Reference Documents
| Document | Content | When to Use |
|---|---|---|
references/best-practices.md | Security, performance, patterns | Every workflow |
references/common-actions.md | Action versions, inputs, outputs | Public action usage |
references/expressions-and-contexts.md | ${{ }} syntax, contexts, functions | Complex conditionals |
references/advanced-triggers.md | workflow_run, dispatch, ChatOps | Workflow orchestration |
references/custom-actions.md | Metadata, structure, versioning | Custom action creation |
references/modern-features.md | Summaries, environments, containers | Enhanced workflows |
Templates
| Template | Location |
|---|---|
| Basic Workflow | assets/templates/workflow/basic_workflow.yml |
| Reusable Workflow | assets/templates/workflow/reusable_workflow.yml |
| Composite Action | assets/templates/action/composite/action.yml |
| Docker Action | assets/templates/action/docker/ |
| JavaScript Action | assets/templates/action/javascript/ |
---
Common Patterns
Matrix Testing
strategy:
matrix:
os: [ubuntu-latest, windows-latest]
node: [18, 20, 22]
fail-fast: falseConditional Deployment
deploy:
if: github.event_name == 'push' && github.ref == 'refs/heads/main'Artifact Sharing
# Upload
- uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: build-${{ github.sha }}
path: dist/
# Download (in dependent job)
- uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: build-${{ github.sha }}Third-Party Action Citation Block
Third-party action citations:
- actions/checkout: https://github.com/actions/checkout (version: v6.0.2, sha: de0fac2e4500dabe0009e67214ff5f5447ce83dd, accessed: 2026-02-28)---
Done Criteria
The task is complete only when all checks below pass:
1. The request route was selected using the trigger decision tree. 2. Only the minimum required references/templates were loaded first. 3. Every third-party action is pinned to a commit SHA and has source/version citation. 4. Validation was run, or a skip exception/fallback path was explicitly documented. 5. Output includes assumptions, security-sensitive decisions (permissions/secrets), and generated file paths.
---
Workflow Summary
1. Route the request using the trigger decision tree 2. Load the minimum references/templates for that route 3. Generate using mandatory security and naming standards 4. Cite and pin third-party actions (source, version, SHA) 5. Validate with devops-skills:github-actions-validator (or documented fallback) 6. Fix and re-validate until clean 7. Present validated output with citations, assumptions, and file paths
name: '[ACTION_NAME]'
description: '[ACTION_DESCRIPTION]'
author: '[AUTHOR_NAME]'
# Define inputs that users can provide
inputs:
[input-name]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
default: '[DEFAULT_VALUE]'
[input-name-2]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
# Define outputs that the action produces
outputs:
[output-name]:
description: '[OUTPUT_DESCRIPTION]'
value: ${{ steps.[step-id].outputs.[output-name] }}
[output-name-2]:
description: '[OUTPUT_DESCRIPTION]'
value: ${{ steps.[step-id].outputs.[output-name-2] }}
# Branding (optional - for GitHub Marketplace)
branding:
icon: '[ICON_NAME]' # Feather icon name
color: '[COLOR]' # white, yellow, blue, green, orange, red, purple, gray-dark
runs:
using: 'composite'
steps:
# Step 1: Validation
- name: Validate inputs
shell: bash
run: |
if [ -z "${{ inputs.[input-name] }}" ]; then
echo "::error::Input [input-name] is required"
exit 1
fi
echo "✅ Inputs validated"
# Step 2: Setup
- name: [SETUP_STEP_NAME]
uses: "[SETUP_ACTION]@[SHA]" # [VERSION]
with:
[SETUP_INPUT]: ${{ inputs.[input-name] }}
# Step 3: Main logic
- name: [MAIN_STEP_NAME]
id: [step-id]
shell: bash
env:
INPUT_VALUE: ${{ inputs.[input-name] }}
run: |
echo "::group::Running [MAIN_STEP_NAME]"
# Main action logic here
[MAIN_COMMAND]
# Set outputs
echo "[output-name]=[OUTPUT_VALUE]" >> $GITHUB_OUTPUT
echo "[output-name-2]=[OUTPUT_VALUE_2]" >> $GITHUB_OUTPUT
echo "::endgroup::"
# Step 4: Post-processing (optional)
- name: Post-processing
if: success()
shell: bash
run: |
echo "✅ Action completed successfully"
echo "Output: ${{ steps.[step-id].outputs.[output-name] }}"
# Step 5: Cleanup (optional)
- name: Cleanup
if: always()
shell: bash
run: |
# Cleanup logic here
echo "Cleanup completed"
name: '[ACTION_NAME]'
description: '[ACTION_DESCRIPTION]'
author: '[AUTHOR_NAME]'
# Define inputs that users can provide
inputs:
[input-name]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
default: '[DEFAULT_VALUE]'
[input-name-2]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
# Define outputs that the action produces
outputs:
[output-name]:
description: '[OUTPUT_DESCRIPTION]'
# Branding (optional - for GitHub Marketplace)
branding:
icon: '[ICON_NAME]' # Feather icon name
color: '[COLOR]' # white, yellow, blue, green, orange, red, purple, gray-dark
runs:
using: 'docker'
image: 'Dockerfile'
# Optional: Use pre-built image instead
# image: 'docker://[REGISTRY]/[IMAGE]:[TAG]'
# Arguments passed to container entrypoint
args:
- ${{ inputs.[input-name] }}
- ${{ inputs.[input-name-2] }}
# Environment variables
env:
[ENV_VAR]: ${{ inputs.[input-name] }}
# Optional: Override entrypoint
# entrypoint: '/entrypoint.sh'
# Base image
FROM [BASE_IMAGE]:[TAG]
# Install required tools with minimal packages, then clean package caches
RUN [PACKAGE_MANAGER] update && \
[PACKAGE_MANAGER] install [PACKAGE_INSTALL_FLAGS] \
[PACKAGE_1] \
[PACKAGE_2] \
&& [CLEANUP_COMMAND]
# Set working directory
WORKDIR /app
# Copy application files
COPY entrypoint.sh /entrypoint.sh
# Make entrypoint executable
RUN chmod +x /entrypoint.sh
# Create and switch to a non-root runtime user (replace placeholders per base image)
RUN [CREATE_NON_ROOT_USER_COMMAND]
USER [RUNTIME_USER]
# Set entrypoint
ENTRYPOINT ["/entrypoint.sh"]
#!/bin/bash
set -e
# Action entrypoint script
# Arguments are passed from action.yml
[INPUT_1]="$1"
[INPUT_2]="$2"
# Validate inputs
if [ -z "$[INPUT_1]" ]; then
echo "::error::[INPUT_1] is required"
exit 1
fi
echo "::group::Running [ACTION_NAME]"
echo "Input 1: $[INPUT_1]"
echo "Input 2: $[INPUT_2]"
# Main action logic
[MAIN_COMMAND]
# Set outputs
echo "[output-name]=[OUTPUT_VALUE]" >> "$GITHUB_OUTPUT"
echo "::endgroup::"
echo "✅ Action completed successfully"
name: '[ACTION_NAME]'
description: '[ACTION_DESCRIPTION]'
author: '[AUTHOR_NAME]'
# Define inputs that users can provide
inputs:
github-token:
description: 'GitHub token for API access'
required: false
default: ${{ github.token }}
[input-name]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
default: '[DEFAULT_VALUE]'
# Define outputs that the action produces
outputs:
[output-name]:
description: '[OUTPUT_DESCRIPTION]'
# Branding (optional - for GitHub Marketplace)
branding:
icon: '[ICON_NAME]' # Feather icon name
color: '[COLOR]' # white, yellow, blue, green, orange, red, purple, gray-dark
runs:
using: 'node20'
main: 'dist/index.js'
# Optional: Run code before action
# pre: 'dist/pre.js'
# Optional: Run code after action
# post: 'dist/post.js'
const core = require('@actions/core');
const github = require('@actions/github');
async function run() {
try {
// Get inputs
const githubToken = core.getInput('github-token');
const inputName = core.getInput('[input-name]', { required: true });
// Log inputs (mask sensitive data)
core.info(`Input: ${inputName}`);
// Create GitHub client
const octokit = github.getOctokit(githubToken);
// Get context information
const context = github.context;
core.info(`Repository: ${context.repo.owner}/${context.repo.repo}`);
core.info(`Event: ${context.eventName}`);
// Main action logic
core.startGroup('Running [ACTION_NAME]');
// Example: Get repository information
const { data: repo } = await octokit.rest.repos.get({
owner: context.repo.owner,
repo: context.repo.repo,
});
core.info(`Repository stars: ${repo.stargazers_count}`);
// [ADD YOUR LOGIC HERE]
core.endGroup();
// Set outputs
core.setOutput('[output-name]', '[OUTPUT_VALUE]');
// Success
core.info('✅ Action completed successfully');
} catch (error) {
// Handle errors
core.setFailed(`Action failed: ${error.message}`);
if (error.stack) {
core.debug(error.stack);
}
}
}
run();
{
"name": "[ACTION_NAME]",
"version": "1.0.0",
"description": "[ACTION_DESCRIPTION]",
"main": "dist/index.js",
"scripts": {
"build": "ncc build index.js -o dist --source-map --license licenses.txt",
"test": "jest",
"lint": "eslint ."
},
"keywords": [
"github-actions",
"[KEYWORD_1]",
"[KEYWORD_2]"
],
"author": "[AUTHOR_NAME]",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0"
},
"devDependencies": {
"@vercel/ncc": "^0.38.1",
"eslint": "^8.57.0",
"jest": "^29.7.0"
}
}
# [WORKFLOW_NAME]
#
# [WORKFLOW_DESCRIPTION]
#
# Triggers:
# - [TRIGGER_EVENTS]
#
# Required secrets:
# - [SECRET_NAME]: [SECRET_DESCRIPTION]
#
# Required permissions:
# - [PERMISSION_SCOPE]: [read/write]
name: [WORKFLOW_NAME]
on:
# Trigger on push to specific branches
push:
branches: [main, develop]
paths:
- '[PATH_PATTERN]/**'
- '!**.md'
# Trigger on pull requests
pull_request:
branches: [main]
types: [opened, synchronize, reopened]
# Manual trigger
workflow_dispatch:
inputs:
environment:
description: 'Environment to deploy'
required: true
type: choice
options:
- dev
- staging
- production
default: 'dev'
debug:
description: 'Enable debug mode'
required: false
type: boolean
default: false
# Scheduled trigger (daily at 2 AM UTC)
schedule:
- cron: '0 2 * * *'
# Set default permissions to read-only (security best practice)
permissions:
contents: read
# Prevent duplicate runs
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
# Environment variables available to all jobs
env:
[ENV_VAR_NAME]: '[ENV_VAR_VALUE]'
jobs:
# Job 1: Linting and validation
lint:
name: Lint and Validate
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
- name: [SETUP_STEP_NAME]
uses: [SETUP_ACTION]
with:
[SETUP_INPUT]: [SETUP_VALUE]
cache: '[CACHE_TYPE]'
- name: Install dependencies
run: [INSTALL_COMMAND]
- name: Run linter
run: [LINT_COMMAND]
# Job 2: Testing
test:
name: Test on [PLATFORM] [VERSION]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
permissions:
contents: read
checks: write # For test reporting
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
[VERSION_KEY]: [[VERSION_1], [VERSION_2], [VERSION_3]]
exclude:
# Exclude expensive combinations if needed
- os: macos-latest
[VERSION_KEY]: [VERSION_1]
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "[SETUP_STEP_NAME] ${{ matrix.[VERSION_KEY] }}"
uses: [SETUP_ACTION]
with:
[VERSION_INPUT]: ${{ matrix.[VERSION_KEY] }}
cache: '[CACHE_TYPE]'
- name: Install dependencies
run: [INSTALL_COMMAND]
- name: Run tests
run: [TEST_COMMAND]
- name: Upload test results
if: always()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: test-results-${{ matrix.os }}-${{ matrix.[VERSION_KEY] }}
path: [TEST_RESULTS_PATH]
retention-days: 7
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.[VERSION_KEY] == [MAIN_VERSION]
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: [COVERAGE_FILE_PATH]
fail_ci_if_error: true
# Job 3: Build
build:
name: Build Application
needs: [lint, test]
runs-on: ubuntu-latest
timeout-minutes: 20
permissions:
contents: read
outputs:
build-id: ${{ steps.build.outputs.id }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: [SETUP_STEP_NAME]
uses: [SETUP_ACTION]
with:
[SETUP_INPUT]: [SETUP_VALUE]
cache: '[CACHE_TYPE]'
- name: Install dependencies
run: [INSTALL_COMMAND]
- name: Build application
id: build
run: |
[BUILD_COMMAND]
echo "id=build-${{ github.sha }}" >> $GITHUB_OUTPUT
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: build-${{ github.sha }}
path: [BUILD_OUTPUT_PATH]
retention-days: 7
if-no-files-found: error
# Job 4: Deploy (conditional)
deploy:
name: Deploy to [ENVIRONMENT]
needs: build
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
timeout-minutes: 15
# Use environment for deployment protection rules
environment:
name: [ENVIRONMENT_NAME]
url: [ENVIRONMENT_URL]
permissions:
contents: read
deployments: write
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Download build artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: build-${{ github.sha }}
path: [BUILD_OUTPUT_PATH]
- name: Deploy to [ENVIRONMENT]
env:
[DEPLOY_SECRET]: ${{ secrets.[DEPLOY_SECRET] }}
run: |
[DEPLOY_COMMAND]
- name: Verify deployment
run: |
[VERIFICATION_COMMAND]
- name: Notify on failure
if: failure()
run: |
echo "Deployment failed!"
# Add notification logic here
# Job 5: Cleanup (always runs)
cleanup:
name: Cleanup
needs: [deploy]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
steps:
- name: Cleanup temporary resources
run: |
echo "Performing cleanup..."
# Add cleanup logic here
# [REUSABLE_WORKFLOW_NAME]
#
# [WORKFLOW_DESCRIPTION]
#
# Inputs:
# - [INPUT_NAME]: [INPUT_DESCRIPTION]
#
# Secrets:
# - [SECRET_NAME]: [SECRET_DESCRIPTION]
#
# Outputs:
# - [OUTPUT_NAME]: [OUTPUT_DESCRIPTION]
#
# Usage:
# jobs:
# call-workflow:
# uses: [OWNER]/[REPO]/.github/workflows/[FILENAME]@[REF]
# with:
# [INPUT_NAME]: [INPUT_VALUE]
# secrets:
# [SECRET_NAME]: ${{ secrets.[SECRET_NAME] }}
name: [REUSABLE_WORKFLOW_NAME]
on:
workflow_call:
inputs:
[INPUT_NAME]:
description: '[INPUT_DESCRIPTION]'
required: [true/false]
type: [string/boolean/number/choice/environment]
default: '[DEFAULT_VALUE]' # Optional, only for optional inputs
# Add more inputs as needed
# Example types:
# - string: text values
# - boolean: true/false
# - number: numeric values
# - choice: predefined options (requires 'options' field)
# - environment: GitHub environment name
secrets:
[SECRET_NAME]:
description: '[SECRET_DESCRIPTION]'
required: [true/false]
# Add more secrets as needed
# Alternatively, use 'secrets: inherit' to pass all secrets
# (use sparingly for security)
outputs:
[OUTPUT_NAME]:
description: '[OUTPUT_DESCRIPTION]'
value: ${{ jobs.[JOB_ID].outputs.[OUTPUT_KEY] }}
# Add more outputs as needed
# Outputs must be mapped from job outputs
# Set default permissions (security best practice)
permissions:
contents: read
# Environment variables available to all jobs
env:
[ENV_VAR_NAME]: '[ENV_VAR_VALUE]'
jobs:
[JOB_ID]:
name: [JOB_NAME]
runs-on: [ubuntu-latest/windows-latest/macos-latest]
timeout-minutes: [TIMEOUT_MINUTES]
# Job-specific permissions
permissions:
contents: read
[PERMISSION_SCOPE]: [read/write]
# Define outputs for this job
outputs:
[OUTPUT_KEY]: ${{ steps.[STEP_ID].outputs.[OUTPUT_NAME] }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 1
- name: [STEP_NAME]
id: [STEP_ID]
run: |
# Use inputs: ${{ inputs.[INPUT_NAME] }}
# Use secrets: ${{ secrets.[SECRET_NAME] }}
echo "[OUTPUT_NAME]=[OUTPUT_VALUE]" >> $GITHUB_OUTPUT
# Add more steps as needed
# Add more jobs as needed
[ANOTHER_JOB_ID]:
name: [ANOTHER_JOB_NAME]
needs: [JOB_ID] # Optional: depend on previous job
runs-on: ubuntu-latest
steps:
- name: Use previous job output
run: |
echo "Output from previous job: ${{ needs.[JOB_ID].outputs.[OUTPUT_KEY] }}"
name: 'Setup Node.js with Smart Caching'
description: 'Setup Node.js with intelligent dependency caching and installation'
author: 'Example Author'
inputs:
node-version:
description: 'Node.js version to use'
required: true
package-manager:
description: 'Package manager to use (npm, yarn, pnpm)'
required: false
default: 'npm'
cache-dependency-path:
description: 'Path to lock file(s)'
required: false
default: '**/package-lock.json'
outputs:
cache-hit:
description: 'Whether cache was hit'
value: ${{ steps.cache.outputs.cache-hit }}
node-version:
description: 'Actual Node.js version installed'
value: ${{ steps.setup.outputs.node-version }}
branding:
icon: 'package'
color: 'green'
runs:
using: 'composite'
steps:
- name: Validate inputs
shell: bash
run: |
if [[ ! "${{ inputs.package-manager }}" =~ ^(npm|yarn|pnpm)$ ]]; then
echo "::error::Invalid package manager: ${{ inputs.package-manager }}"
exit 1
fi
echo "✅ Inputs validated"
- name: Setup Node.js ${{ inputs.node-version }}
id: setup
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ inputs.node-version }}
- name: Enable Corepack for pnpm/yarn
if: inputs.package-manager != 'npm'
shell: bash
run: corepack enable
- name: Get cache directory
id: cache-dir
shell: bash
run: |
if [ "${{ inputs.package-manager }}" == "npm" ]; then
echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
elif [ "${{ inputs.package-manager }}" == "yarn" ]; then
echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
else
echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
fi
- name: Cache dependencies
id: cache
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: ${{ steps.cache-dir.outputs.dir }}
key: ${{ runner.os }}-${{ inputs.package-manager }}-${{ hashFiles(inputs.cache-dependency-path) }}
restore-keys: |
${{ runner.os }}-${{ inputs.package-manager }}-
- name: Install dependencies
if: steps.cache.outputs.cache-hit != 'true'
shell: bash
run: |
if [ "${{ inputs.package-manager }}" == "npm" ]; then
npm ci
elif [ "${{ inputs.package-manager }}" == "yarn" ]; then
yarn install --frozen-lockfile
else
pnpm install --frozen-lockfile
fi
- name: Report status
shell: bash
run: |
echo "::group::Setup Summary"
echo "Node.js version: $(node --version)"
echo "Package manager: ${{ inputs.package-manager }}"
echo "Cache hit: ${{ steps.cache.outputs.cache-hit }}"
echo "::endgroup::"
# Docker BuildKit Cache Example
# Demonstrates: GitHub Actions cache backend, inline vs registry cache, multi-stage build optimization
name: Docker Build with BuildKit Cache
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Example 1: Basic BuildKit with GitHub Actions Cache
build-with-gha-cache:
name: Build with GitHub Actions Cache Backend
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=sha,prefix={{branch}}-
# GitHub Actions Cache Backend
# - Stores cache in GitHub Actions cache (10 GB free per repo)
# - mode=max caches all build layers (not just final image)
# - Fastest option for most use cases
- name: Build and push (GHA cache)
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.sha }}
# Example 2: Multi-Stage Build with Optimized Caching
build-multistage-optimized:
name: Multi-Stage Build with Cache Optimization
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Multi-stage builds benefit most from mode=max
# All intermediate stages are cached
- name: Build multi-stage with optimized cache
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
file: ./Dockerfile.multistage
target: production # Only build production stage
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:multistage-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
NODE_ENV=production
# Example 3: Registry Cache (for cross-runner sharing)
build-with-registry-cache:
name: Build with Registry Cache Backend
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Registry Cache Backend
# - Stores cache in container registry
# - Useful for sharing cache across different CI systems
# - Slower than GHA cache but more portable
# - Good for self-hosted runners
- name: Build and push (registry cache)
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:registry-cache-${{ github.sha }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache
cache-to: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:buildcache,mode=max
# Example 4: Inline Cache (embedded in image)
build-with-inline-cache:
name: Build with Inline Cache
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Inline Cache
# - Embeds cache metadata in the image itself
# - No separate cache artifact
# - Only caches layers in final image (not intermediate stages)
# - Useful when you can't use separate cache backends
- name: Build and push (inline cache)
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:inline-cache-${{ github.sha }}
cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
cache-to: type=inline
# Example 5: Comparison and Best Practices
comparison-summary:
name: Cache Strategy Comparison
runs-on: ubuntu-latest
needs: [build-with-gha-cache, build-multistage-optimized, build-with-registry-cache, build-with-inline-cache]
if: always()
timeout-minutes: 5
steps:
- name: Generate cache strategy comparison
run: |
cat <<'EOF' >> $GITHUB_STEP_SUMMARY
## Docker BuildKit Cache Strategy Comparison
| Strategy | Speed | Cache Size | Use Case | Limitations |
|----------|-------|------------|----------|-------------|
| **GHA Cache** (`type=gha`) | ⚡ Fastest | Up to 10 GB free | Most workflows, GitHub-hosted runners | Limited to 10 GB per repo |
| **Registry Cache** (`type=registry`) | 🐌 Slower | Unlimited | Self-hosted runners, cross-CI sharing | Requires registry storage |
| **Inline Cache** (`type=inline`) | 🐌 Slowest | Embedded | When separate cache unavailable | Only caches final image layers |
### Recommendations
✅ **Use GHA Cache (`type=gha,mode=max`) for:**
- Standard CI/CD workflows
- Multi-stage builds (caches all stages)
- GitHub-hosted runners
- Fast rebuild times
✅ **Use Registry Cache (`type=registry`) for:**
- Self-hosted runners
- Sharing cache across different CI systems
- When you need >10 GB cache
- Cross-repository builds
⚠️ **Use Inline Cache (`type=inline`) only when:**
- You can't use GHA or registry cache
- Cache size is very small
- You only need final image layers cached
### Cache Modes
- `mode=min` - Only caches layers from final image (default)
- `mode=max` - Caches all layers including intermediate stages (recommended)
### Multi-Stage Build Optimization
For multi-stage Dockerfiles:
```dockerfile
# Base stage - cached separately
FROM node:24-alpine AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci
# Build stage - cached separately
FROM base AS build
COPY . .
RUN npm run build
# Production stage - final image
FROM base AS production
COPY --from=build /app/dist ./dist
CMD ["node", "dist/index.js"]
```
With `mode=max`, all stages (base, build, production) are cached independently,
maximizing cache reuse on subsequent builds.
### Cache Key Strategy
BuildKit automatically generates cache keys based on:
- Dockerfile content
- Build context files referenced in COPY/ADD
- Build arguments
- Target platform
No manual cache key management needed! 🎉
EOF
echo "## Build Status Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- GHA Cache: ${{ needs.build-with-gha-cache.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Multi-stage: ${{ needs.build-multistage-optimized.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Registry Cache: ${{ needs.build-with-registry-cache.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Inline Cache: ${{ needs.build-with-inline-cache.result }}" >> $GITHUB_STEP_SUMMARY
GitHub Actions Generator Examples
This directory contains example workflows and actions generated using the github-actions-generator skill.
Workflows
Language-Specific CI Pipelines
nodejs-ci.yml
Complete CI pipeline for Node.js applications demonstrating:
- Matrix testing across multiple Node.js versions and operating systems
- Dependency caching with
actions/setup-node - Parallel linting and testing
- Artifact uploading for test results
- Code coverage reporting with Codecov
- Concurrency controls
Use case: Standard CI/CD for Node.js projects
python-ci.yml
Python CI pipeline demonstrating:
- Matrix testing across Python versions
- Virtual environment management
- Dependency caching with pip
- Testing with pytest
- Code quality checks
Use case: Python application CI/CD
go-ci.yml
Go CI pipeline demonstrating:
- Go module caching
- Cross-platform builds
- Go testing and benchmarking
- Static code analysis
Use case: Go application CI/CD
Container & Deployment Workflows
docker-build-push.yml
Docker image build and push workflow demonstrating:
- Multi-platform builds (amd64, arm64)
- GitHub Container Registry integration
- Docker layer caching with GitHub Actions cache
- Automatic tagging based on git events
- Secure authentication with
GITHUB_TOKEN
Use case: Containerized application deployment
multi-environment-deploy.yml
Multi-environment deployment workflow demonstrating:
- Environment protection rules and approval gates
- Dynamic environment selection (dev, staging, production)
- AWS deployment with OIDC authentication
- Blue-green deployment strategy
- Automatic rollback on failure
- Smoke tests and health checks
- Deployment verification
Use case: Production-grade multi-stage deployments
Advanced Workflow Patterns
monorepo-ci.yml
Monorepo CI pipeline demonstrating:
- Path-based change detection
- Conditional job execution based on affected packages
- Cross-package dependency management
- Parallel builds for independent packages
- Artifact sharing between jobs
- Package-specific test strategies
Use case: Monorepo projects with multiple packages
scheduled-tasks.yml
Scheduled maintenance workflow demonstrating:
- Cron schedule configuration
- Dependency security audits
- Stale branch cleanup
- Cache management
- External service health checks
- Automated issue creation
- Weekly metrics reporting
Use case: Repository maintenance and monitoring
Security Workflows
security/dependency-review.yml
Dependency review workflow demonstrating:
- Pull request dependency scanning
- Vulnerability severity thresholds
- License policy enforcement
- Automatic build failure on policy violations
Use case: Supply chain security for PRs
security/sbom-attestation.yml
SBOM and attestation workflow demonstrating:
- Software Bill of Materials generation
- SBOM attestation with GitHub's signing infrastructure
- Build provenance attestation
- Container vulnerability scanning with Trivy
- Multi-platform container builds
- Security scan results upload to GitHub Security tab
Use case: Supply chain security compliance
Actions
setup-node-cached/action.yml
Composite action for Node.js setup demonstrating:
- Smart dependency caching for npm, yarn, and pnpm
- Input validation
- Multiple package manager support
- Cache hit detection
- Grouped output for better logging
Use case: Reusable Node.js setup across multiple workflows
Usage
These examples can be used as: 1. Templates - Copy and modify for your own projects 2. Learning Resources - Study best practices and patterns 3. Testing - Validate with github-actions-validator skill
Testing Examples
To validate any of these examples:
cd devops-skills-plugin/skills/github-actions-validator
bash scripts/validate_workflow.sh ../../github-actions-generator/examples/workflows/nodejs-ci.ymlBest Practices Demonstrated
All examples follow these best practices:
- ✅ Actions pinned to SHAs with version comments
- ✅ Minimal permissions with explicit
permissions:blocks - ✅ Concurrency controls to prevent duplicate runs
- ✅ Proper timeout settings
- ✅ Semantic naming conventions
- ✅ Comprehensive error handling
- ✅ Security-first approach
# Dependency Review Workflow Example
# Demonstrates: Dependency scanning, license compliance, vulnerability detection
name: Dependency Review
on:
pull_request:
branches: [main, develop]
workflow_dispatch:
permissions:
contents: read
pull-requests: write # For commenting on PRs
jobs:
dependency-review:
name: Review Dependencies and Licenses
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Dependency Review
uses: actions/dependency-review-action@05fe4576374b728f0c523d6a13d64c25081e0803 # v4.8.3
with:
# Fail on critical and high severity vulnerabilities
fail-on-severity: high
# Allow specific licenses (adjust based on your policy)
allow-licenses: >
MIT,
Apache-2.0,
BSD-2-Clause,
BSD-3-Clause,
ISC,
0BSD
# Deny specific licenses (adjust based on your policy)
deny-licenses: >
GPL-3.0,
AGPL-3.0,
LGPL-3.0
# Fail on scopes (optional)
# fail-on-scopes: runtime
# Comment on PR with results (default: true)
comment-summary-in-pr: always
# Allow dependencies from specific sources
# allow-dependencies-licenses: pkg:npm/express@*
- name: Upload dependency graph
if: always()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: dependency-graph
path: dependency-graph.json
retention-days: 7
if-no-files-found: ignore
# SBOM Attestation Workflow Example
# Demonstrates: Software Bill of Materials generation and attestation for container images
name: Build and Attest Container Image
on:
push:
branches: [main]
tags: ['v*']
workflow_dispatch:
permissions:
contents: read
packages: write
id-token: write
attestations: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-attest:
name: Build, Scan, and Attest Container Image
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
id: build
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true
- name: Generate SBOM with Syft
uses: anchore/sbom-action@fbfd9c6c189226748411491745178e0c2017392d # v0.20.10
with:
image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: spdx-json
output-file: sbom.spdx.json
upload-artifact: true
upload-release-assets: false
- name: Attest SBOM
uses: actions/attest-sbom@bd218ad0dbcb3e146bd073d1d9c6d78e08aa8a0b # v2.4.0
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
sbom-path: sbom.spdx.json
push-to-registry: true
- name: Scan image for vulnerabilities with Trivy
uses: aquasecurity/trivy-action@b6643a29fecd7f34b3597bc6acb0a98b03d33ff8 # v0.33.1
with:
image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}@${{ steps.build.outputs.digest }}
format: sarif
output: trivy-results.sarif
severity: CRITICAL,HIGH
- name: Upload Trivy scan results to GitHub Security
uses: github/codeql-action/upload-sarif@ae9ef3a1d2e3413523c3741725c30064970cc0d4 # v3.32.5
if: always()
with:
sarif_file: trivy-results.sarif
category: container-scan
- name: Generate build provenance
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
- name: Summary
run: |
echo "## Container Image Built and Attested ✅" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Digest:** \`${{ steps.build.outputs.digest }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Tags:**" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "${{ steps.meta.outputs.tags }}" >> $GITHUB_STEP_SUMMARY
echo "\`\`\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "✅ SBOM generated and attested" >> $GITHUB_STEP_SUMMARY
echo "✅ Build provenance attested" >> $GITHUB_STEP_SUMMARY
echo "✅ Security scan completed" >> $GITHUB_STEP_SUMMARY
# ChatOps Commands Example
# Demonstrates: issue_comment trigger, PR commands, permission validation, command parsing, feedback
name: ChatOps Commands
on:
issue_comment:
types: [created]
permissions:
contents: read
pull-requests: write
deployments: write
issues: write
jobs:
# Command router - determines which command to run
parse-command:
name: Parse ChatOps Command
# Only run on PRs, not issues
if: github.event.issue.pull_request
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
command: ${{ steps.parse.outputs.command }}
args: ${{ steps.parse.outputs.args }}
is-authorized: ${{ steps.check-permissions.outputs.authorized }}
steps:
- name: Parse command
id: parse
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
COMMENT="$COMMENT_BODY"
# Extract command (first word starting with /)
COMMAND=$(echo "$COMMENT" | grep -oE '^/[-[:alnum:]_]+' | sed 's|^/||' || echo "")
# Extract arguments (everything after command)
ARGS=$(echo "$COMMENT" | sed -E 's|^/[-[:alnum:]_]+\s*||' || echo "")
echo "command=$COMMAND" >> $GITHUB_OUTPUT
echo "args=$ARGS" >> $GITHUB_OUTPUT
echo "Detected command: /$COMMAND"
echo "Arguments: $ARGS"
- name: Check permissions
id: check-permissions
env:
AUTHOR_ASSOCIATION: ${{ github.event.comment.author_association }}
COMMAND: ${{ steps.parse.outputs.command }}
run: |
# Define allowed associations per command
case "$COMMAND" in
deploy|approve|merge)
# Sensitive commands - only owners and members
ALLOWED="OWNER MEMBER"
;;
run-tests|benchmark|lint)
# Less sensitive - include collaborators
ALLOWED="OWNER MEMBER COLLABORATOR"
;;
help|status|info)
# Public commands - anyone
ALLOWED="OWNER MEMBER COLLABORATOR CONTRIBUTOR FIRST_TIME_CONTRIBUTOR FIRST_TIMER NONE"
;;
*)
# Unknown command - no one
ALLOWED=""
;;
esac
if echo "$ALLOWED" | grep -q "$AUTHOR_ASSOCIATION"; then
echo "authorized=true" >> $GITHUB_OUTPUT
echo "✅ User authorized: ${{ github.event.comment.user.login }} ($AUTHOR_ASSOCIATION)"
else
echo "authorized=false" >> $GITHUB_OUTPUT
echo "❌ User not authorized: ${{ github.event.comment.user.login }} ($AUTHOR_ASSOCIATION)"
fi
- name: React to unauthorized command
if: steps.check-permissions.outputs.authorized == 'false'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: '-1'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `❌ @${{ github.event.comment.user.login }} You don't have permission to run this command.\n\nRequired role: OWNER or MEMBER`
});
# Command: /deploy [environment]
cmd-deploy:
name: Deploy Command
needs: [parse-command]
if: |
needs.parse-command.outputs.command == 'deploy' &&
needs.parse-command.outputs.is-authorized == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: React with rocket
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'rocket'
});
- name: Parse deploy arguments
id: parse-deploy
run: |
ARGS="${{ needs.parse-command.outputs.args }}"
# Extract environment (default: staging)
ENV=$(echo "$ARGS" | awk '{print $1}' | tr '[:upper:]' '[:lower:]')
ENV=${ENV:-staging}
# Validate environment
if [[ ! "$ENV" =~ ^(dev|staging|production)$ ]]; then
echo "error=Invalid environment: $ENV" >> $GITHUB_OUTPUT
exit 1
fi
echo "environment=$ENV" >> $GITHUB_OUTPUT
- name: Get PR details
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
result-encoding: string
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number
});
core.setOutput('ref', pr.data.head.ref);
core.setOutput('sha', pr.data.head.sha);
core.setOutput('mergeable', pr.data.mergeable);
- name: Check if PR is mergeable
if: steps.pr.outputs.mergeable == 'false'
run: |
echo "❌ PR has merge conflicts"
exit 1
- name: Checkout PR code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ steps.pr.outputs.ref }}
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
cache: 'npm'
- name: Install and build
run: |
npm ci
npm run build
- name: Deploy to environment
id: deploy
env:
ENVIRONMENT: ${{ steps.parse-deploy.outputs.environment }}
run: |
echo "🚀 Deploying to $ENVIRONMENT"
# Deployment logic here
DEPLOY_URL="https://$ENVIRONMENT.example.com"
echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT
- name: Comment deployment success
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const environment = '${{ steps.parse-deploy.outputs.environment }}';
const url = '${{ steps.deploy.outputs.url }}';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## ✅ Deployment Successful
**Environment:** ${environment}
**URL:** ${url}
**Triggered by:** @${{ github.event.comment.user.login }}
**Commit:** ${{ steps.pr.outputs.sha }}
[View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`
});
- name: Comment deployment failure
if: failure()
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## ❌ Deployment Failed
[View workflow run](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`
});
# Command: /run-tests [suite]
cmd-run-tests:
name: Run Tests Command
needs: [parse-command]
if: |
needs.parse-command.outputs.command == 'run-tests' &&
needs.parse-command.outputs.is-authorized == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: React with eyes
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'eyes'
});
- name: Parse test suite
id: parse-suite
run: |
ARGS="${{ needs.parse-command.outputs.args }}"
SUITE=$(echo "$ARGS" | awk '{print $1}')
SUITE=${SUITE:-all}
echo "suite=$SUITE" >> $GITHUB_OUTPUT
- name: Get PR details
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number
});
core.setOutput('ref', pr.data.head.ref);
- name: Checkout PR code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ steps.pr.outputs.ref }}
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
id: test
env:
TEST_SUITE: ${{ steps.parse-suite.outputs.suite }}
run: |
echo "🧪 Running test suite: $TEST_SUITE"
case "$TEST_SUITE" in
unit)
npm run test:unit
;;
integration)
npm run test:integration
;;
e2e)
npm run test:e2e
;;
all)
npm test
;;
*)
echo "❌ Unknown test suite: $TEST_SUITE"
exit 1
;;
esac
- name: Comment test results
if: always()
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const suite = '${{ steps.parse-suite.outputs.suite }}';
const success = '${{ steps.test.outcome }}' === 'success';
const emoji = success ? '✅' : '❌';
const status = success ? 'Passed' : 'Failed';
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## ${emoji} Test Suite: ${suite} - ${status}
[View test results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`
});
# Command: /benchmark
cmd-benchmark:
name: Benchmark Command
needs: [parse-command]
if: |
needs.parse-command.outputs.command == 'benchmark' &&
needs.parse-command.outputs.is-authorized == 'true'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: React with chart
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: '+1'
});
- name: Get PR details
id: pr
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const pr = await github.rest.pulls.get({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: context.payload.issue.number
});
core.setOutput('ref', pr.data.head.ref);
- name: Checkout PR code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ steps.pr.outputs.ref }}
- name: Run benchmarks
run: |
echo "⚡ Running performance benchmarks"
# Benchmark logic here
- name: Comment benchmark results
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## ⚡ Benchmark Results
| Metric | Value | Baseline | Change |
|--------|-------|----------|--------|
| Response Time | 125ms | 130ms | -3.8% 🟢 |
| Throughput | 1,250 req/s | 1,200 req/s | +4.2% 🟢 |
| Memory Usage | 245 MB | 250 MB | -2.0% 🟢 |
✅ All metrics within acceptable range
[View details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`
});
# Command: /help
cmd-help:
name: Help Command
needs: [parse-command]
if: needs.parse-command.outputs.command == 'help'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Show help
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `## 🤖 ChatOps Commands
Available commands:
### Deployment
- \`/deploy [env]\` - Deploy PR to environment (dev, staging, production)
- Example: \`/deploy staging\`
- Requires: OWNER or MEMBER permission
### Testing
- \`/run-tests [suite]\` - Run test suite (unit, integration, e2e, all)
- Example: \`/run-tests unit\`
- Requires: OWNER, MEMBER, or COLLABORATOR permission
### Performance
- \`/benchmark\` - Run performance benchmarks
- Requires: OWNER, MEMBER, or COLLABORATOR permission
### Utility
- \`/help\` - Show this help message
- Available to everyone
---
**Your permission level:** ${{ github.event.comment.author_association }}
`
});
# Unknown command handler
cmd-unknown:
name: Unknown Command
needs: [parse-command]
if: |
needs.parse-command.outputs.command != '' &&
needs.parse-command.outputs.command != 'deploy' &&
needs.parse-command.outputs.command != 'run-tests' &&
needs.parse-command.outputs.command != 'benchmark' &&
needs.parse-command.outputs.command != 'help'
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: React with confused
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
await github.rest.reactions.createForIssueComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: context.payload.comment.id,
content: 'confused'
});
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: `❓ Unknown command: \`/${{ needs.parse-command.outputs.command }}\`
Type \`/help\` to see available commands.
`
});
# Repository Dispatch Example
# Demonstrates: External API triggers, webhook integration, payload validation, multiple event types
name: Handle External Events
on:
repository_dispatch:
types:
- deploy-prod
- deploy-staging
- deploy-dev
- run-migration
- rebuild-cache
- incident-response
- custom-task
permissions:
contents: read
deployments: write
issues: write
env:
AWS_REGION: 'us-east-1'
jobs:
# Job 1: Handle deployment events
handle-deployment:
name: Handle Deployment Event
if: startsWith(github.event.action, 'deploy-')
runs-on: ubuntu-latest
timeout-minutes: 30
environment:
name: ${{ github.event.client_payload.environment || 'staging' }}
steps:
- name: Validate deployment payload
id: validate
run: |
# Required fields validation
if [[ -z "${{ github.event.client_payload.version }}" ]]; then
echo "❌ Error: 'version' is required in client_payload"
exit 1
fi
if [[ -z "${{ github.event.client_payload.environment }}" ]]; then
echo "❌ Error: 'environment' is required in client_payload"
exit 1
fi
# Validate environment
ENV="${{ github.event.client_payload.environment }}"
if [[ ! "$ENV" =~ ^(dev|staging|production)$ ]]; then
echo "❌ Error: Invalid environment: $ENV"
echo "Allowed: dev, staging, production"
exit 1
fi
# Validate version format (semver)
VERSION="${{ github.event.client_payload.version }}"
if [[ ! "$VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?$ ]]; then
echo "⚠️ Warning: Version doesn't match semver format: $VERSION"
fi
echo "environment=$ENV" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "requestor=${{ github.event.client_payload.requestor || 'unknown' }}" >> $GITHUB_OUTPUT
- name: Log deployment request
run: |
cat <<EOF
📦 Deployment Request Received
Event Type: ${{ github.event.action }}
Version: ${{ steps.validate.outputs.version }}
Environment: ${{ steps.validate.outputs.environment }}
Requestor: ${{ steps.validate.outputs.requestor }}
Rollback: ${{ github.event.client_payload.rollback || 'false' }}
Force: ${{ github.event.client_payload.force || 'false' }}
Payload:
${{ toJSON(github.event.client_payload) }}
EOF
- name: Checkout specific version
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ steps.validate.outputs.version }}
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
env:
NODE_ENV: production
ENVIRONMENT: ${{ steps.validate.outputs.environment }}
run: npm run build
- name: Deploy to environment
env:
ENVIRONMENT: ${{ steps.validate.outputs.environment }}
VERSION: ${{ steps.validate.outputs.version }}
run: |
echo "🚀 Deploying $VERSION to $ENVIRONMENT"
# Deployment logic here
# Example: AWS, Kubernetes, etc.
DEPLOY_URL="https://$ENVIRONMENT.example.com"
echo "url=$DEPLOY_URL" >> $GITHUB_OUTPUT
- name: Create deployment notification issue
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const environment = '${{ steps.validate.outputs.environment }}';
const version = '${{ steps.validate.outputs.version }}';
const requestor = '${{ steps.validate.outputs.requestor }}';
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Deployment: ${version} to ${environment}`,
body: `## 🚀 Deployment Summary
**Version:** ${version}
**Environment:** ${environment}
**Requestor:** ${requestor}
**Triggered via:** repository_dispatch API
**Workflow Run:** https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}
**URL:** https://${environment}.example.com
✅ Deployment completed successfully
`,
labels: ['deployment', environment]
});
# Job 2: Handle migration events
handle-migration:
name: Run Database Migration
if: github.event.action == 'run-migration'
runs-on: ubuntu-latest
timeout-minutes: 15
environment:
name: ${{ github.event.client_payload.environment || 'staging' }}
steps:
- name: Validate migration payload
run: |
if [[ -z "${{ github.event.client_payload.migration_name }}" ]]; then
echo "❌ Error: 'migration_name' is required"
exit 1
fi
if [[ -z "${{ github.event.client_payload.environment }}" ]]; then
echo "❌ Error: 'environment' is required"
exit 1
fi
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Run migration
env:
MIGRATION_NAME: ${{ github.event.client_payload.migration_name }}
ENVIRONMENT: ${{ github.event.client_payload.environment }}
run: |
echo "🔄 Running migration: $MIGRATION_NAME on $ENVIRONMENT"
# Migration logic here
# Example: npm run migrate:up $MIGRATION_NAME
echo "✅ Migration completed"
# Job 3: Handle cache rebuild
handle-cache-rebuild:
name: Rebuild Application Cache
if: github.event.action == 'rebuild-cache'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
- name: Clear existing cache
run: |
echo "🗑️ Clearing cache for: ${{ github.event.client_payload.cache_key || 'all' }}"
# Clear cache logic
- name: Rebuild cache
run: |
echo "🔄 Rebuilding cache..."
npm ci
npm run build
- name: Save cache
uses: actions/cache/save@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: |
~/.npm
node_modules
dist/
key: cache-rebuild-${{ github.sha }}
# Job 4: Handle incident response
handle-incident:
name: Incident Response
if: github.event.action == 'incident-response'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Parse incident payload
id: incident
run: |
SEVERITY="${{ github.event.client_payload.severity || 'medium' }}"
MESSAGE="${{ github.event.client_payload.message }}"
AFFECTED_SERVICE="${{ github.event.client_payload.service }}"
echo "severity=$SEVERITY" >> $GITHUB_OUTPUT
echo "message=$MESSAGE" >> $GITHUB_OUTPUT
echo "service=$AFFECTED_SERVICE" >> $GITHUB_OUTPUT
- name: Create incident issue
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const severity = '${{ steps.incident.outputs.severity }}';
const message = '${{ steps.incident.outputs.message }}';
const service = '${{ steps.incident.outputs.service }}';
const severityEmoji = {
'critical': '🔴',
'high': '🟠',
'medium': '🟡',
'low': '🟢'
};
const emoji = severityEmoji[severity] || '⚪';
const issue = await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${emoji} Incident: ${service} - ${severity.toUpperCase()}`,
body: `## Incident Report
**Severity:** ${severity.toUpperCase()}
**Service:** ${service}
**Message:** ${message}
**Triggered:** ${new Date().toISOString()}
**Source:** Monitoring System (via repository_dispatch)
### Next Steps
- [ ] Investigate root cause
- [ ] Implement fix
- [ ] Deploy fix
- [ ] Verify resolution
- [ ] Post-mortem
`,
labels: ['incident', severity, 'automated'],
assignees: context.payload.client_payload.assignees || []
});
console.log(`Created incident issue: ${issue.data.html_url}`);
- name: Trigger automated response
if: steps.incident.outputs.severity == 'critical'
run: |
echo "🚨 CRITICAL INCIDENT DETECTED"
echo "Triggering automated response procedures..."
# Automated incident response
# Example: Scale up services, enable debug mode, alert team
# Job 5: Handle custom tasks
handle-custom-task:
name: Execute Custom Task
if: github.event.action == 'custom-task'
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Validate custom task
run: |
if [[ -z "${{ github.event.client_payload.task_name }}" ]]; then
echo "❌ Error: 'task_name' is required"
exit 1
fi
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Execute custom task
env:
TASK_NAME: ${{ github.event.client_payload.task_name }}
TASK_ARGS: ${{ toJSON(github.event.client_payload.args) }}
run: |
echo "⚙️ Executing task: $TASK_NAME"
echo "Arguments: $TASK_ARGS"
# Custom task execution
# Use allowlist to prevent arbitrary code execution
case "$TASK_NAME" in
"cleanup-old-artifacts")
echo "Running artifact cleanup..."
# Cleanup logic
;;
"regenerate-docs")
echo "Regenerating documentation..."
# Docs generation logic
;;
"validate-config")
echo "Validating configuration..."
# Validation logic
;;
*)
echo "❌ Error: Unknown task: $TASK_NAME"
echo "Allowed tasks: cleanup-old-artifacts, regenerate-docs, validate-config"
exit 1
;;
esac
echo "✅ Task completed"
# Job 6: Event summary
event-summary:
name: Event Processing Summary
runs-on: ubuntu-latest
needs: [handle-deployment, handle-migration, handle-cache-rebuild, handle-incident, handle-custom-task]
if: always()
timeout-minutes: 5
steps:
- name: Generate summary
run: |
cat <<'EOF' >> $GITHUB_STEP_SUMMARY
## 📬 Repository Dispatch Event Summary
**Event Type:** ${{ github.event.action }}
**Triggered:** ${{ github.event.created_at }}
**Sender:** ${{ github.event.sender.login }}
### Job Results
- Deployment: ${{ needs.handle-deployment.result }}
- Migration: ${{ needs.handle-migration.result }}
- Cache Rebuild: ${{ needs.handle-cache-rebuild.result }}
- Incident Response: ${{ needs.handle-incident.result }}
- Custom Task: ${{ needs.handle-custom-task.result }}
### Payload
```json
${{ toJSON(github.event.client_payload) }}
```
---
### How to Trigger This Workflow
**Using curl:**
```bash
curl -X POST \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
https://api.github.com/repos/${{ github.repository }}/dispatches \
-d '{
"event_type": "deploy-prod",
"client_payload": {
"version": "v1.2.3",
"environment": "production",
"requestor": "api-user"
}
}'
```
**Using Python:**
```python
import requests
url = "https://api.github.com/repos/${{ github.repository }}/dispatches"
headers = {
"Authorization": f"token {token}",
"Accept": "application/vnd.github.v3+json"
}
payload = {
"event_type": "deploy-prod",
"client_payload": {
"version": "v1.2.3",
"environment": "production"
}
}
requests.post(url, json=payload, headers=headers)
```
**Using GitHub CLI:**
```bash
gh api repos/${{ github.repository }}/dispatches \
-X POST \
-f event_type='deploy-prod' \
-f client_payload[version]='v1.2.3' \
-f client_payload[environment]='production'
```
EOF
# Workflow Orchestration Example
# Demonstrates: workflow_run trigger, workflow chaining, artifact passing, secure external PR handling
# This is the MAIN CI workflow that runs on all PRs
name: CI Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
checks: write
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '24'
jobs:
# Standard CI jobs - safe for external PRs
test:
name: Run Tests
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run tests
run: npm test -- --coverage
- name: Upload coverage
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: coverage-report
path: coverage/
retention-days: 7
build:
name: Build Application
runs-on: ubuntu-latest
needs: [test]
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
env:
NODE_ENV: production
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: build-artifacts
path: dist/
retention-days: 30
ci-status:
name: CI Status Check
runs-on: ubuntu-latest
needs: [test, build]
if: always()
timeout-minutes: 5
steps:
- name: Check CI status
run: |
if [[ "${{ needs.test.result }}" == "failure" ]] || [[ "${{ needs.build.result }}" == "failure" ]]; then
echo "❌ CI failed"
exit 1
fi
echo "✅ CI passed"
---
# SEPARATE WORKFLOW FILE: .github/workflows/security-scan.yml
# This workflow runs AFTER CI completes, triggered by workflow_run
# It's safe for external PRs because it runs with the target branch's code
name: Security Scan
on:
workflow_run:
workflows: ["CI Pipeline"]
types: [completed]
# Critical: These permissions are safe because this workflow
# uses code from the target branch, not the PR
permissions:
security-events: write
contents: read
actions: read
issues: write # Required for PR comments via issues.createComment
jobs:
security-scan:
name: Run Security Scan
# Only scan if CI passed
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
# Important: Check out the PR's code explicitly
# This is safe because we're in workflow_run (target branch context)
- name: Checkout PR code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.workflow_run.head_sha }}
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: '24'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run dependency audit
run: |
npm audit --audit-level=high || true
npm audit --json > audit-results.json || true
- name: Run CodeQL analysis
uses: github/codeql-action/init@ae9ef3a1d2e3413523c3741725c30064970cc0d4 # v3.32.5
with:
languages: javascript
- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@ae9ef3a1d2e3413523c3741725c30064970cc0d4 # v3.32.5
with:
category: security-scan
- name: Upload security scan results
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
if: always()
with:
name: security-scan-results
path: |
audit-results.json
retention-days: 30
- name: Comment on PR with results
if: github.event.workflow_run.event == 'pull_request'
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const fs = require('fs');
// Get PR number from workflow run
const prNumber = context.payload.workflow_run.pull_requests[0]?.number;
if (!prNumber) {
console.log('Not a PR, skipping comment');
return;
}
// Read audit results
let auditResults = {};
try {
auditResults = JSON.parse(fs.readFileSync('audit-results.json', 'utf8'));
} catch (error) {
console.log('No audit results found');
}
const vulnerabilities = auditResults.metadata?.vulnerabilities || {};
const total = Object.values(vulnerabilities).reduce((a, b) => a + b, 0);
let message = '## 🔒 Security Scan Results\n\n';
if (total === 0) {
message += '✅ No vulnerabilities found!\n';
} else {
message += '⚠️ Found vulnerabilities:\n\n';
message += `- Critical: ${vulnerabilities.critical || 0}\n`;
message += `- High: ${vulnerabilities.high || 0}\n`;
message += `- Moderate: ${vulnerabilities.moderate || 0}\n`;
message += `- Low: ${vulnerabilities.low || 0}\n`;
}
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: prNumber,
body: message
});
---
# SEPARATE WORKFLOW FILE: .github/workflows/deploy.yml
# This workflow deploys to staging/production AFTER CI completes successfully
name: Deploy Application
on:
workflow_run:
workflows: ["CI Pipeline"]
types: [completed]
branches: [main, staging]
permissions:
contents: read
deployments: write
id-token: write
jobs:
determine-environment:
name: Determine Deployment Environment
runs-on: ubuntu-latest
# Only deploy if CI passed
if: ${{ github.event.workflow_run.conclusion == 'success' }}
timeout-minutes: 5
outputs:
environment: ${{ steps.determine.outputs.environment }}
should-deploy: ${{ steps.determine.outputs.should-deploy }}
steps:
- name: Determine environment
id: determine
run: |
BRANCH="${{ github.event.workflow_run.head_branch }}"
case "$BRANCH" in
main)
echo "environment=production" >> $GITHUB_OUTPUT
echo "should-deploy=true" >> $GITHUB_OUTPUT
;;
staging)
echo "environment=staging" >> $GITHUB_OUTPUT
echo "should-deploy=true" >> $GITHUB_OUTPUT
;;
*)
echo "environment=none" >> $GITHUB_OUTPUT
echo "should-deploy=false" >> $GITHUB_OUTPUT
;;
esac
echo "Deploying to: ${{ steps.determine.outputs.environment }}"
deploy:
name: Deploy to ${{ needs.determine-environment.outputs.environment }}
runs-on: ubuntu-latest
needs: [determine-environment]
if: needs.determine-environment.outputs.should-deploy == 'true'
timeout-minutes: 20
environment:
name: ${{ needs.determine-environment.outputs.environment }}
url: https://${{ needs.determine-environment.outputs.environment }}.example.com
steps:
# Download artifacts from the CI workflow
- name: Download build artifacts from CI
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: build-artifacts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Display downloaded files
run: |
echo "Downloaded artifacts:"
ls -lah
- name: Deploy to environment
env:
ENVIRONMENT: ${{ needs.determine-environment.outputs.environment }}
run: |
echo "Deploying to $ENVIRONMENT"
echo "Commit SHA: ${{ github.event.workflow_run.head_sha }}"
echo "Branch: ${{ github.event.workflow_run.head_branch }}"
# Deployment commands here
# Example: Upload to S3, deploy to Kubernetes, etc.
- name: Run smoke tests
env:
ENVIRONMENT: ${{ needs.determine-environment.outputs.environment }}
run: |
URL="https://$ENVIRONMENT.example.com"
echo "Running smoke tests against $URL"
# Basic health check
curl -f "$URL/health" || exit 1
echo "✅ Smoke tests passed"
- name: Create deployment summary
run: |
cat <<EOF >> $GITHUB_STEP_SUMMARY
## 🚀 Deployment Complete
**Environment:** ${{ needs.determine-environment.outputs.environment }}
**Commit:** ${{ github.event.workflow_run.head_sha }}
**Branch:** ${{ github.event.workflow_run.head_branch }}
**URL:** https://${{ needs.determine-environment.outputs.environment }}.example.com
### CI Workflow Details
- **Workflow:** ${{ github.event.workflow_run.name }}
- **Run ID:** ${{ github.event.workflow_run.id }}
- **Conclusion:** ${{ github.event.workflow_run.conclusion }}
✅ Deployment successful!
EOF
---
# SEPARATE WORKFLOW FILE: .github/workflows/performance-test.yml
# Run performance tests after CI completes (only on main branch)
name: Performance Tests
on:
workflow_run:
workflows: ["CI Pipeline"]
types: [completed]
branches: [main]
permissions:
contents: write # Required for repos.createCommitComment
actions: read
jobs:
performance-test:
name: Run Performance Tests
if: ${{ github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ github.event.workflow_run.head_sha }}
- name: Download build artifacts from CI
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: build-artifacts
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run performance benchmarks
run: |
echo "Running performance tests..."
# Performance test commands here
# Example: k6, lighthouse, etc.
- name: Upload performance results
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: performance-results
path: performance-results/
retention-days: 30
- name: Comment with performance results
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const message = `## ⚡ Performance Test Results
**Commit:** ${{ github.event.workflow_run.head_sha }}
✅ Performance tests completed
[View detailed results](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})
`;
await github.rest.repos.createCommitComment({
owner: context.repo.owner,
repo: context.repo.repo,
commit_sha: '${{ github.event.workflow_run.head_sha }}',
body: message
});
# Docker Build and Push Example
# Demonstrates: Multi-platform builds, Docker layer caching, GitHub Container Registry
name: Build and Push Docker Image
on:
push:
branches: [main]
tags: ['v*']
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
packages: write
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
name: Build and Push Docker Image
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0
- name: Log in to GitHub Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha,prefix={{branch}}-
- name: Build and push Docker image
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
VERSION=${{ github.sha }}
BUILD_DATE=${{ github.event.head_commit.timestamp || github.event.repository.updated_at }}
# Go CI Pipeline Example
# Demonstrates: Matrix testing, caching, linting, testing, code coverage
name: Go CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
GO_VERSION: '1.22'
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Run golangci-lint
uses: golangci/golangci-lint-action@e7fa5ac41e1cf5b7d48e45e42232ce7ada589601 # v9.1.0
with:
version: v1.55.3
args: --timeout=5m
- name: Run go vet
run: go vet ./...
- name: Run go fmt check
run: |
if [ "$(gofmt -s -l . | wc -l)" -gt 0 ]; then
echo "Code is not formatted. Run 'go fmt ./...'"
gofmt -s -l .
exit 1
fi
test:
name: Test on Go ${{ matrix.go-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
permissions:
contents: read
checks: write
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
go-version: ['1.21', '1.22', '1.23']
exclude:
- os: macos-latest
go-version: '1.21'
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go ${{ matrix.go-version }}
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: ${{ matrix.go-version }}
cache: true
cache-dependency-path: go.sum
- name: Download dependencies
run: go mod download
- name: Verify dependencies
run: go mod verify
- name: Run tests
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Generate coverage report
if: matrix.os == 'ubuntu-latest'
run: go tool cover -html=coverage.out -o coverage.html
- name: Upload test results
if: always()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: test-results-${{ matrix.os }}-go${{ matrix.go-version }}
path: |
coverage.out
coverage.html
retention-days: 7
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.go-version == '1.22'
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.out
fail_ci_if_error: true
build:
name: Build Application
needs: [lint, test]
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
matrix:
goos: [linux, darwin, windows]
goarch: [amd64, arm64]
exclude:
- goos: windows
goarch: arm64
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Go
uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
with:
go-version: ${{ env.GO_VERSION }}
cache: true
cache-dependency-path: go.sum
- name: Build binary
env:
GOOS: ${{ matrix.goos }}
GOARCH: ${{ matrix.goarch }}
CGO_ENABLED: 0
run: |
OUTPUT_NAME="app-${{ matrix.goos }}-${{ matrix.goarch }}"
if [ "${{ matrix.goos }}" = "windows" ]; then
OUTPUT_NAME="${OUTPUT_NAME}.exe"
fi
go build -ldflags="-s -w" -o "${OUTPUT_NAME}" .
ls -lh "${OUTPUT_NAME}"
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
path: app-*
retention-days: 7
if-no-files-found: error
# Monorepo CI Pipeline Example
# Demonstrates: Path filtering, affected package detection, conditional builds, matrix for packages
name: Monorepo CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '24'
jobs:
# Detect which packages have changed
detect-changes:
name: Detect Changed Packages
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
packages: ${{ steps.filter.outputs.changes }}
any-changed: ${{ steps.filter.outputs.any-changed }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0 # Need full history for path detection
- name: Detect changed paths
uses: dorny/paths-filter@de90cc6551a0c30ca4af50ac82dafbf57eb22fab # v3.0.2
id: filter
with:
filters: |
frontend:
- 'packages/frontend/**'
backend:
- 'packages/backend/**'
shared:
- 'packages/shared/**'
infrastructure:
- 'packages/infrastructure/**'
- name: Set any-changed flag
id: any-changed
run: |
if [[ "${{ steps.filter.outputs.frontend }}" == "true" ]] || \
[[ "${{ steps.filter.outputs.backend }}" == "true" ]] || \
[[ "${{ steps.filter.outputs.shared }}" == "true" ]] || \
[[ "${{ steps.filter.outputs.infrastructure }}" == "true" ]]; then
echo "any-changed=true" >> $GITHUB_OUTPUT
else
echo "any-changed=false" >> $GITHUB_OUTPUT
fi
# Lint root configuration files
lint-root:
name: Lint Root Configuration
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install root dependencies
run: npm ci
- name: Lint root files
run: npm run lint:root
# Build and test shared package (dependency for others)
shared:
name: Build Shared Package
runs-on: ubuntu-latest
needs: [detect-changes]
if: needs.detect-changes.outputs.packages contains 'shared' || needs.detect-changes.outputs.any-changed == 'true'
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
working-directory: packages/shared
- name: Run linter
run: npm run lint
working-directory: packages/shared
- name: Run tests
run: npm test
working-directory: packages/shared
- name: Build package
run: npm run build
working-directory: packages/shared
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: shared-dist
path: packages/shared/dist/
retention-days: 1
# Build and test frontend package
frontend:
name: Build Frontend Package
runs-on: ubuntu-latest
needs: [detect-changes, shared]
if: needs.detect-changes.outputs.packages contains 'frontend' || needs.detect-changes.outputs.packages contains 'shared'
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Download shared package
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: shared-dist
path: packages/shared/dist/
- name: Install dependencies
run: npm ci
working-directory: packages/frontend
- name: Run linter
run: npm run lint
working-directory: packages/frontend
- name: Run tests
run: npm test
working-directory: packages/frontend
- name: Build frontend
run: npm run build
working-directory: packages/frontend
env:
NODE_ENV: production
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: frontend-dist
path: packages/frontend/dist/
retention-days: 7
# Build and test backend package
backend:
name: Build Backend Package
runs-on: ubuntu-latest
needs: [detect-changes, shared]
if: needs.detect-changes.outputs.packages contains 'backend' || needs.detect-changes.outputs.packages contains 'shared'
timeout-minutes: 20
strategy:
matrix:
node-version: [20, 24]
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Download shared package
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: shared-dist
path: packages/shared/dist/
- name: Install dependencies
run: npm ci
working-directory: packages/backend
- name: Run linter
run: npm run lint
working-directory: packages/backend
- name: Run unit tests
run: npm run test:unit
working-directory: packages/backend
- name: Run integration tests
run: npm run test:integration
working-directory: packages/backend
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
- name: Build backend
run: npm run build
working-directory: packages/backend
- name: Upload build artifacts
if: matrix.node-version == 24
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: backend-dist
path: packages/backend/dist/
retention-days: 7
# Validate infrastructure code
infrastructure:
name: Validate Infrastructure
runs-on: ubuntu-latest
needs: [detect-changes]
if: needs.detect-changes.outputs.packages contains 'infrastructure'
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Terraform
uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3.1.2
with:
terraform_version: 1.6.0
- name: Terraform Format Check
run: terraform fmt -check -recursive
working-directory: packages/infrastructure
- name: Terraform Init
run: terraform init -backend=false
working-directory: packages/infrastructure
- name: Terraform Validate
run: terraform validate
working-directory: packages/infrastructure
# Integration test across packages
integration:
name: Integration Tests
runs-on: ubuntu-latest
needs: [frontend, backend]
if: |
always() &&
(needs.frontend.result == 'success' || needs.frontend.result == 'skipped') &&
(needs.backend.result == 'success' || needs.backend.result == 'skipped')
timeout-minutes: 20
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
- name: Download all artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
- name: Install test dependencies
run: npm ci
working-directory: tests/integration
- name: Run integration tests
run: npm test
working-directory: tests/integration
# Summary job - gates deployment
check-status:
name: CI Status Check
runs-on: ubuntu-latest
needs: [lint-root, shared, frontend, backend, infrastructure, integration]
if: always()
timeout-minutes: 5
steps:
- name: Check all job statuses
run: |
echo "Lint Root: ${{ needs.lint-root.result }}"
echo "Shared: ${{ needs.shared.result }}"
echo "Frontend: ${{ needs.frontend.result }}"
echo "Backend: ${{ needs.backend.result }}"
echo "Infrastructure: ${{ needs.infrastructure.result }}"
echo "Integration: ${{ needs.integration.result }}"
# Fail if any job failed (skipped is ok)
if [[ "${{ needs.lint-root.result }}" == "failure" ]] || \
[[ "${{ needs.shared.result }}" == "failure" ]] || \
[[ "${{ needs.frontend.result }}" == "failure" ]] || \
[[ "${{ needs.backend.result }}" == "failure" ]] || \
[[ "${{ needs.infrastructure.result }}" == "failure" ]] || \
[[ "${{ needs.integration.result }}" == "failure" ]]; then
echo "❌ One or more jobs failed"
exit 1
fi
echo "✅ All jobs passed or were skipped"
- name: Generate summary
run: |
echo "## Monorepo CI Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Status:** ✅ All checks passed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Job Results" >> $GITHUB_STEP_SUMMARY
echo "- Lint Root: ${{ needs.lint-root.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Shared Package: ${{ needs.shared.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Frontend Package: ${{ needs.frontend.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Backend Package: ${{ needs.backend.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Infrastructure: ${{ needs.infrastructure.result }}" >> $GITHUB_STEP_SUMMARY
echo "- Integration Tests: ${{ needs.integration.result }}" >> $GITHUB_STEP_SUMMARY
# Multi-Environment Deployment Example
# Demonstrates: Environment protection rules, approval gates, deployment strategies, rollback
name: Multi-Environment Deployment
on:
push:
branches: [main, develop, staging]
release:
types: [published]
workflow_dispatch:
inputs:
environment:
description: 'Target environment'
required: true
type: choice
options:
- dev
- staging
- production
default: 'dev'
skip-tests:
description: 'Skip pre-deployment tests'
required: false
type: boolean
default: false
permissions:
contents: read
deployments: write
id-token: write # For OIDC authentication
concurrency:
# Prevent concurrent deployments to same environment
group: deploy-${{ github.event.inputs.environment || (github.ref == 'refs/heads/main' && 'production' || github.ref == 'refs/heads/staging' && 'staging' || 'dev') }}
cancel-in-progress: false
env:
NODE_VERSION: '24'
AWS_REGION: 'us-east-1'
jobs:
# Determine deployment environment based on trigger
determine-environment:
name: Determine Deployment Environment
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
environment: ${{ steps.determine.outputs.environment }}
deploy: ${{ steps.determine.outputs.deploy }}
steps:
- name: Determine environment
id: determine
run: |
# Manual dispatch takes precedence
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "environment=${{ github.event.inputs.environment }}" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
exit 0
fi
# Release event goes to production
if [ "${{ github.event_name }}" == "release" ]; then
echo "environment=production" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
exit 0
fi
# Branch-based deployment
case "${{ github.ref }}" in
refs/heads/main)
echo "environment=production" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
;;
refs/heads/staging)
echo "environment=staging" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
;;
refs/heads/develop)
echo "environment=dev" >> $GITHUB_OUTPUT
echo "deploy=true" >> $GITHUB_OUTPUT
;;
*)
echo "environment=dev" >> $GITHUB_OUTPUT
echo "deploy=false" >> $GITHUB_OUTPUT
;;
esac
# Build application
build:
name: Build Application
runs-on: ubuntu-latest
needs: [determine-environment]
if: needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 20
outputs:
artifact-name: ${{ steps.build.outputs.artifact-name }}
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
if: github.event.inputs.skip-tests != 'true'
run: npm test
- name: Build application
id: build
env:
NODE_ENV: production
ENVIRONMENT: ${{ needs.determine-environment.outputs.environment }}
run: |
npm run build
ARTIFACT_NAME="build-${{ github.sha }}"
echo "artifact-name=$ARTIFACT_NAME" >> $GITHUB_OUTPUT
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: ${{ steps.build.outputs.artifact-name }}
path: dist/
retention-days: 30
# Deploy to Development
deploy-dev:
name: Deploy to Development
runs-on: ubuntu-latest
needs: [determine-environment, build]
if: needs.determine-environment.outputs.environment == 'dev' && needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 15
environment:
name: dev
url: https://dev.example.com
steps:
- name: Download build artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: ${{ needs.build.outputs.artifact-name }}
path: dist/
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
role-to-assume: ${{ secrets.AWS_ROLE_DEV }}
role-session-name: github-actions-dev-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to S3
run: |
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_DEV }} --delete
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DIST_DEV }} \
--paths "/*"
- name: Run smoke tests
run: |
echo "Running smoke tests against dev environment..."
curl -f https://dev.example.com/health || exit 1
echo "✅ Smoke tests passed"
# Deploy to Staging (with approval requirement)
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: [determine-environment, build]
if: needs.determine-environment.outputs.environment == 'staging' && needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 20
environment:
name: staging
url: https://staging.example.com
steps:
- name: Download build artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: ${{ needs.build.outputs.artifact-name }}
path: dist/
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
role-to-assume: ${{ secrets.AWS_ROLE_STAGING }}
role-session-name: github-actions-staging-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Deploy to S3
run: |
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_STAGING }} --delete
- name: Invalidate CloudFront cache
run: |
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DIST_STAGING }} \
--paths "/*"
- name: Run smoke tests
run: |
echo "Running smoke tests against staging environment..."
curl -f https://staging.example.com/health || exit 1
echo "✅ Smoke tests passed"
- name: Run integration tests
run: |
echo "Running integration tests..."
# Add your integration test commands here
echo "✅ Integration tests passed"
# Deploy to Production (with approval requirement and blue-green strategy)
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: [determine-environment, build]
if: needs.determine-environment.outputs.environment == 'production' && needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 30
environment:
name: production
url: https://example.com
steps:
- name: Download build artifacts
uses: actions/download-artifact@c850b930e6ba138125429b7e5c93fc707a7f8427 # v4.1.4
with:
name: ${{ needs.build.outputs.artifact-name }}
path: dist/
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@61815dcd50bd041e203e49132bacad1fd04d2708 # v5.1.1
with:
role-to-assume: ${{ secrets.AWS_ROLE_PRODUCTION }}
role-session-name: github-actions-prod-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Backup current deployment
id: backup
run: |
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_BUCKET="s3://${{ secrets.S3_BACKUP_BUCKET }}/backups/${TIMESTAMP}"
echo "Creating backup at ${BACKUP_BUCKET}"
aws s3 sync s3://${{ secrets.S3_BUCKET_PRODUCTION }} ${BACKUP_BUCKET}
echo "backup-path=${BACKUP_BUCKET}" >> $GITHUB_OUTPUT
- name: Deploy to production (Blue-Green)
id: deploy
run: |
# Deploy to blue environment first
echo "Deploying to blue environment..."
aws s3 sync dist/ s3://${{ secrets.S3_BUCKET_PRODUCTION_BLUE }} --delete
# Update CloudFront to point to blue environment
echo "Switching traffic to blue environment..."
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DIST_PRODUCTION }} \
--paths "/*"
- name: Run smoke tests (production)
id: smoke-tests
run: |
echo "Running smoke tests against production..."
# Wait for CloudFront to propagate
sleep 30
# Basic health check
curl -f https://example.com/health || exit 1
# Check critical endpoints
curl -f https://example.com/api/status || exit 1
echo "✅ Production smoke tests passed"
- name: Monitor metrics
run: |
echo "Monitoring deployment metrics for 5 minutes..."
# In real scenario, this would monitor CloudWatch metrics
# Error rates, latency, etc.
sleep 10
echo "✅ Metrics look good"
- name: Rollback on failure
if: failure()
run: |
echo "❌ Deployment failed, initiating rollback..."
# Restore from backup
aws s3 sync ${{ steps.backup.outputs.backup-path }} s3://${{ secrets.S3_BUCKET_PRODUCTION }} --delete
# Invalidate cache
aws cloudfront create-invalidation \
--distribution-id ${{ secrets.CLOUDFRONT_DIST_PRODUCTION }} \
--paths "/*"
echo "🔄 Rollback completed"
exit 1
# Deployment verification
post-deployment-verification:
name: Post-Deployment Verification
runs-on: ubuntu-latest
needs: [determine-environment, deploy-dev, deploy-staging, deploy-production]
if: always() && needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 10
steps:
- name: Verify deployment
env:
ENVIRONMENT: ${{ needs.determine-environment.outputs.environment }}
run: |
case "${ENVIRONMENT}" in
dev)
URL="https://dev.example.com"
;;
staging)
URL="https://staging.example.com"
;;
production)
URL="https://example.com"
;;
esac
echo "Verifying deployment at ${URL}"
# Extended health check
for i in {1..5}; do
if curl -f "${URL}/health"; then
echo "✅ Health check passed (attempt $i)"
break
else
echo "⚠️ Health check failed (attempt $i/5)"
if [ $i -eq 5 ]; then
echo "❌ Health check failed after 5 attempts"
exit 1
fi
sleep 10
fi
done
- name: Update deployment status
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const environment = '${{ needs.determine-environment.outputs.environment }}';
const devResult = '${{ needs.deploy-dev.result }}';
const stagingResult = '${{ needs.deploy-staging.result }}';
const productionResult = '${{ needs.deploy-production.result }}';
let deploymentResult = 'skipped';
if (environment === 'dev') deploymentResult = devResult;
if (environment === 'staging') deploymentResult = stagingResult;
if (environment === 'production') deploymentResult = productionResult;
const status = deploymentResult === 'success' ? '✅' : '❌';
core.summary
.addHeading(`${status} Deployment to ${environment.toUpperCase()}`)
.addRaw(`**Environment:** ${environment}`)
.addBreak()
.addRaw(`**Status:** ${deploymentResult}`)
.addBreak()
.addRaw(`**Commit:** ${context.sha}`)
.addBreak()
.addRaw(`**Actor:** ${context.actor}`)
.write();
# Notify stakeholders
notify:
name: Notify Stakeholders
runs-on: ubuntu-latest
needs: [determine-environment, post-deployment-verification]
if: always() && needs.determine-environment.outputs.deploy == 'true'
timeout-minutes: 5
steps:
- name: Send notification
env:
ENVIRONMENT: ${{ needs.determine-environment.outputs.environment }}
VERIFICATION_RESULT: ${{ needs.post-deployment-verification.result }}
run: |
STATUS_EMOJI="✅"
if [ "$VERIFICATION_RESULT" != "success" ]; then
STATUS_EMOJI="❌"
fi
echo "${STATUS_EMOJI} Deployment to ${ENVIRONMENT} completed with status: ${VERIFICATION_RESULT}"
# In real scenario, send to Slack/Teams/Email
# Example: curl -X POST $SLACK_WEBHOOK -d "{\"text\":\"${STATUS_EMOJI} Deployment to ${ENVIRONMENT}: ${VERIFICATION_RESULT}\"}"
# Node.js CI Pipeline Example
# Demonstrates: Matrix testing, caching, artifact uploading, code coverage
name: Node.js CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
NODE_VERSION: '24'
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run ESLint
run: npm run lint
test:
name: Test on Node ${{ matrix.node-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
permissions:
contents: read
checks: write
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [20, 22, 24]
exclude:
- os: macos-latest
node-version: 20
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Upload test results
if: always()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: test-results-${{ matrix.os }}-${{ matrix.node-version }}
path: test-results/
retention-days: 7
- name: Upload coverage
if: matrix.os == 'ubuntu-latest' && matrix.node-version == 24
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
build:
name: Build Application
needs: [lint, test]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Node.js
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build application
run: npm run build
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: build-${{ github.sha }}
path: dist/
retention-days: 7
if-no-files-found: error
# Python CI Pipeline Example
# Demonstrates: Matrix testing, caching, linting, testing, code coverage
name: Python CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: '3.12'
jobs:
lint:
name: Lint Code
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Python
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install linting tools
run: |
python -m pip install --upgrade pip
pip install black flake8 mypy pylint isort
- name: Run Black
run: black --check .
- name: Run isort
run: isort --check-only .
- name: Run Flake8
run: flake8 .
- name: Run Pylint
run: |
FILES=$(git ls-files '*.py')
if [ -z "$FILES" ]; then
echo "No Python files found to lint"
exit 0
fi
pylint $FILES
- name: Run MyPy
run: mypy .
test:
name: Test on Python ${{ matrix.python-version }}
runs-on: ${{ matrix.os }}
timeout-minutes: 30
permissions:
contents: read
checks: write
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
python-version: ['3.10', '3.11', '3.12']
exclude:
- os: macos-latest
python-version: '3.10'
fail-fast: false
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Python ${{ matrix.python-version }}
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: |
requirements.txt
requirements-dev.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install -r requirements-dev.txt
- name: Run tests with pytest
run: |
pytest --cov=. --cov-report=xml --cov-report=html --junitxml=junit.xml
- name: Upload test results
if: always()
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: test-results-${{ matrix.os }}-py${{ matrix.python-version }}
path: |
junit.xml
htmlcov/
retention-days: 7
- name: Upload coverage to Codecov
if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'
uses: codecov/codecov-action@e0b68c6749509c5f83f984dd99a76a1c1a231044 # v4.0.1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: true
build:
name: Build Package
needs: [lint, test]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup Python
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: 'pip'
- name: Install build tools
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Upload build artifacts
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: python-package-distributions
path: dist/
retention-days: 7
if-no-files-found: error
Related skills
How it compares
Use github-actions-generator when you need opinionated, validator-checked GitHub YAML rather than copying unpinned workflow snippets from generic templates.
FAQ
Does github-actions-generator validate generated workflows?
github-actions-generator automatically invokes the devops-skills:github-actions-validator skill after generation, fixes reported issues, and re-validates until the workflow or action.yml passes mandatory checks.
What GitHub Actions artifacts can github-actions-generator create?
github-actions-generator can scaffold standard .github/workflows CI/CD files, custom local action.yml packages, workflow_call reusable pipelines, and security-focused workflows such as dependency review, SBOM, or CodeQL scanning.