Skip to content

feat: add modular framework infrastructure primitives #110

feat: add modular framework infrastructure primitives

feat: add modular framework infrastructure primitives #110

Workflow file for this run

name: CI and Release
on:
push:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
- '.github/ISSUE_TEMPLATE/**'
- '.github/PULL_REQUEST_TEMPLATE.md'
- '.github/FUNDING.yml'
- 'LICENSE'
pull_request:
branches: [main]
paths-ignore:
- 'docs/**'
- '*.md'
- '.github/ISSUE_TEMPLATE/**'
- '.github/PULL_REQUEST_TEMPLATE.md'
- '.github/FUNDING.yml'
- 'LICENSE'
workflow_dispatch:
inputs:
prerelease_suffix:
description: 'Pre-release channel suffix (e.g. rc.1, beta.2). Leave empty for standard CI build.'
required: false
default: ''
permissions:
contents: read
checks: write
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
env:
DOTNET_NOLOGO: true
DOTNET_CLI_TELEMETRY_OPTOUT: true
NUKE_TELEMETRY_OPTOUT: true
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
ENABLE_NUGET: 'true'
NUGET_USE_OIDC: 'false'
ENABLE_INSTALLERS: 'true'
ENABLE_ANDROID: 'false'
ENABLE_IOS: 'false'
jobs:
resolve-version:
name: Resolve Version
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
version: ${{ steps.resolve.outputs.version }}
full_version: ${{ steps.resolve.outputs.full_version }}
version_suffix: ${{ steps.resolve.outputs.version_suffix }}
tag: ${{ steps.resolve.outputs.tag }}
sha: ${{ steps.resolve.outputs.sha }}
is_release: ${{ steps.resolve.outputs.is_release }}
build_matrix: ${{ steps.matrix.outputs.build_matrix }}
enable_nuget: ${{ steps.matrix.outputs.enable_nuget }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-tags: true
- name: Resolve version and context
id: resolve
shell: bash
env:
PRERELEASE_SUFFIX: ${{ inputs.prerelease_suffix }}
EVENT_NAME: ${{ github.event_name }}
GIT_REF: ${{ github.ref }}
RUN_NUMBER: ${{ github.run_number }}
run: |
SEMVER_REGEX='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'
VERSION=$(perl -0777 -ne 'if (m{<VersionPrefix>\s*([^<]+?)\s*</VersionPrefix>}s) { print $1 }' Directory.Build.props | head -n1 | xargs)
if [ -z "$VERSION" ]; then
echo "::error::VersionPrefix not found in Directory.Build.props"
exit 1
fi
if ! [[ "$VERSION" =~ $SEMVER_REGEX ]]; then
echo "::error::Invalid VersionPrefix: $VERSION"
exit 1
fi
SHA="$(git rev-parse HEAD)"
TAG="v$VERSION"
IS_RELEASE="false"
if [ "$EVENT_NAME" != "pull_request" ] && [ "$GIT_REF" = "refs/heads/main" ]; then
git fetch --tags --force
if git ls-remote --tags origin "refs/tags/$TAG" | grep -q "$TAG"; then
echo "::notice::Tag $TAG already exists. Version not bumped — skipping release."
else
IS_RELEASE="true"
fi
fi
VERSION_SUFFIX=""
if [ -n "$PRERELEASE_SUFFIX" ]; then
if ! [[ "$PRERELEASE_SUFFIX" =~ ^[0-9A-Za-z.-]+$ ]]; then
echo "::error::Invalid prerelease_suffix: must match [0-9A-Za-z.-]+"
exit 1
fi
VERSION_SUFFIX="$PRERELEASE_SUFFIX"
elif [ "$IS_RELEASE" != "true" ]; then
VERSION_SUFFIX="ci.${RUN_NUMBER}"
fi
if [ -n "$VERSION_SUFFIX" ]; then
FULL_VERSION="${VERSION}-${VERSION_SUFFIX}"
else
FULL_VERSION="${VERSION}"
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "full_version=$FULL_VERSION" >> "$GITHUB_OUTPUT"
echo "version_suffix=$VERSION_SUFFIX" >> "$GITHUB_OUTPUT"
echo "tag=$TAG" >> "$GITHUB_OUTPUT"
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
echo "is_release=$IS_RELEASE" >> "$GITHUB_OUTPUT"
echo "Resolved: version=$VERSION full_version=$FULL_VERSION tag=$TAG sha=$SHA is_release=$IS_RELEASE"
- name: Version summary
shell: bash
run: |
VERSION="${{ steps.resolve.outputs.version }}"
FULL_VERSION="${{ steps.resolve.outputs.full_version }}"
VERSION_SUFFIX="${{ steps.resolve.outputs.version_suffix }}"
IS_RELEASE="${{ steps.resolve.outputs.is_release }}"
SHA="${{ steps.resolve.outputs.sha }}"
TAG="${{ steps.resolve.outputs.tag }}"
IFS='.' read -r CUR_MAJOR CUR_MINOR CUR_PATCH <<< "$VERSION"
PREV_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
PREV_VER="${PREV_TAG#v}"
IFS='.' read -r PRE_MAJOR PRE_MINOR PRE_PATCH <<< "$PREV_VER"
if [ "$CUR_MAJOR" != "$PRE_MAJOR" ]; then
BUMP="MAJOR"
elif [ "$CUR_MINOR" != "$PRE_MINOR" ]; then
BUMP="MINOR"
elif [ "$CUR_PATCH" != "$PRE_PATCH" ]; then
BUMP="PATCH"
else
BUMP="NONE (same as $PREV_TAG)"
fi
else
PREV_TAG="(none)"
BUMP="INITIAL"
fi
MODE=$( [ "$IS_RELEASE" = "true" ] && echo "Release" || echo "CI" )
CHANNEL="stable"
if [ -n "$VERSION_SUFFIX" ]; then
CHANNEL="$VERSION_SUFFIX"
fi
REPO="${{ github.repository }}"
OWNER="${REPO%%/*}"
REPO_NAME="${REPO##*/}"
DOCS_URL="https://${OWNER,,}.github.io/${REPO_NAME}/"
{
echo "## Version Info"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| **Version** | \`$VERSION\` |"
echo "| **Full Version** | \`$FULL_VERSION\` |"
echo "| **Channel** | \`$CHANNEL\` |"
echo "| **Tag** | \`$TAG\` |"
echo "| **Previous Tag** | \`$PREV_TAG\` |"
echo "| **Bump Type** | **$BUMP** |"
echo "| **Mode** | $MODE |"
echo "| **Commit** | \`${SHA::8}\` |"
echo "| **Expected Docs URL** | $DOCS_URL |"
echo "| **Docs Auto-Deploy** | Enable Pages (GitHub Actions) + add \`docs/package.json\` |"
} >> "$GITHUB_STEP_SUMMARY"
- name: Compute build matrix
id: matrix
shell: bash
run: |
IS_RELEASE="${{ steps.resolve.outputs.is_release }}"
PACK=$( [ "$ENABLE_NUGET" = "true" ] && echo "true" || echo "false" )
PUBLISH=$( [ "$ENABLE_INSTALLERS" = "true" ] && echo "true" || echo "false" )
echo "enable_nuget=$PACK" >> "$GITHUB_OUTPUT"
if [ "$IS_RELEASE" = "true" ]; then
ENTRIES="{\"os\":\"ubuntu-latest\",\"runtime\":\"linux-x64\",\"pack\":$PACK,\"publish\":$PUBLISH,\"workloads\":\"\"}"
ENTRIES+=",{\"os\":\"windows-latest\",\"runtime\":\"win-x64\",\"pack\":false,\"publish\":$PUBLISH,\"workloads\":\"\"}"
ENTRIES+=",{\"os\":\"macos-latest\",\"runtime\":\"osx-arm64\",\"pack\":false,\"publish\":$PUBLISH,\"workloads\":\"\"}"
if [ "$ENABLE_ANDROID" = "true" ]; then
ENTRIES+=',{"os":"ubuntu-latest","runtime":"android","pack":false,"publish":false,"workloads":"android"}'
fi
if [ "$ENABLE_IOS" = "true" ]; then
ENTRIES+=',{"os":"macos-latest","runtime":"ios","pack":false,"publish":false,"workloads":"ios"}'
fi
else
ENTRIES="{\"os\":\"ubuntu-latest\",\"runtime\":\"linux-x64\",\"pack\":$PACK,\"publish\":false,\"workloads\":\"\"}"
ENTRIES+=",{\"os\":\"windows-latest\",\"runtime\":\"win-x64\",\"pack\":false,\"publish\":false,\"workloads\":\"\"}"
ENTRIES+=",{\"os\":\"macos-latest\",\"runtime\":\"osx-arm64\",\"pack\":false,\"publish\":false,\"workloads\":\"\"}"
if [ "$ENABLE_ANDROID" = "true" ]; then
ENTRIES+=',{"os":"ubuntu-latest","runtime":"android","pack":false,"publish":false,"workloads":"android"}'
fi
if [ "$ENABLE_IOS" = "true" ]; then
ENTRIES+=',{"os":"macos-latest","runtime":"ios","pack":false,"publish":false,"workloads":"ios"}'
fi
fi
MATRIX="{\"include\":[$ENTRIES]}"
echo "build_matrix=$MATRIX" >> "$GITHUB_OUTPUT"
echo "Matrix: $MATRIX"
build-and-test:
name: Build and Test (${{ matrix.runtime }})
needs: resolve-version
runs-on: ${{ matrix.os }}
timeout-minutes: 30
env:
BuildNumber: ${{ github.event_name != 'pull_request' && github.run_number || '' }}
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.resolve-version.outputs.build_matrix) }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.resolve-version.outputs.sha }}
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: global.json
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.nuget/packages
key: ${{ runner.os }}-nuget-${{ hashFiles('**/*.csproj', 'Directory.Packages.props', 'global.json', '.config/dotnet-tools.json') }}
restore-keys: ${{ runner.os }}-nuget-
- name: Install .NET workloads
if: matrix.workloads != ''
run: dotnet workload install ${{ matrix.workloads }}
- name: Mark build script executable
if: runner.os != 'Windows'
run: chmod +x build.sh
- name: Check code format
if: matrix.runtime == 'linux-x64'
shell: bash
run: ./build.sh Format
- name: Build and test
if: runner.os != 'Windows'
shell: bash
run: ./build.sh Test --VersionSuffix "${{ needs.resolve-version.outputs.version_suffix }}"
- name: Build and test (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: ./build.ps1 Test --VersionSuffix "${{ needs.resolve-version.outputs.version_suffix }}"
- name: Generate coverage report
if: matrix.runtime == 'linux-x64'
shell: bash
run: ./build.sh CoverageReport --skip Test
- name: Upload coverage report
if: matrix.runtime == 'linux-x64'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: coverage-report
path: artifacts/test-results/coverage-report/**
if-no-files-found: warn
- name: Pack
if: matrix.pack
shell: bash
run: ./build.sh Pack --skip Build Test
- name: Generate release manifest
if: needs.resolve-version.outputs.is_release == 'true' && matrix.pack
shell: bash
run: ./build.sh GenerateReleaseManifest --skip Build Test Pack
- name: Validate package versions
if: needs.resolve-version.outputs.is_release == 'true' && matrix.pack
shell: bash
run: ./build.sh ValidatePackageVersions --skip Build Test Pack
- name: Publish app
if: needs.resolve-version.outputs.is_release == 'true' && matrix.publish && runner.os != 'Windows'
shell: bash
run: ./build.sh Publish --Runtime "${{ matrix.runtime }}"
- name: Publish app (Windows)
if: needs.resolve-version.outputs.is_release == 'true' && matrix.publish && runner.os == 'Windows'
shell: pwsh
run: ./build.ps1 Publish --Runtime "${{ matrix.runtime }}"
- name: Package app
if: needs.resolve-version.outputs.is_release == 'true' && matrix.publish && runner.os != 'Windows'
shell: bash
run: ./build.sh PackageApp --Runtime "${{ matrix.runtime }}" --skip Publish
- name: Package app (Windows)
if: needs.resolve-version.outputs.is_release == 'true' && matrix.publish && runner.os == 'Windows'
shell: pwsh
run: ./build.ps1 PackageApp --Runtime "${{ matrix.runtime }}" --skip Publish
- name: Upload test results
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: test-results-${{ matrix.runtime }}
path: artifacts/test-results/**
if-no-files-found: warn
- name: Report test results
if: always() && !cancelled()
uses: dorny/test-reporter@a43b3a5f7366b97d083190328d2c652e1a8b6aa2 # v3.0.0
with:
name: Tests (${{ matrix.runtime }})
path: artifacts/test-results/**/*.trx
reporter: dotnet-trx
- name: Upload packages
if: needs.resolve-version.outputs.is_release == 'true' && matrix.pack
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: packages
path: artifacts/packages/**
if-no-files-found: error
- name: Upload installer artifacts
if: needs.resolve-version.outputs.is_release == 'true' && matrix.publish
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: installer-${{ matrix.runtime }}
path: artifacts/installers/**
if-no-files-found: error
build-docs:
name: Build Documentation
needs: resolve-version
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
has_docs: ${{ steps.check.outputs.has_docs }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.resolve-version.outputs.sha }}
- name: Check for documentation source
id: check
shell: bash
run: |
if [ -f "docs/package.json" ]; then
if [ ! -f "docs/package-lock.json" ]; then
echo "::error::docs/package.json exists but docs/package-lock.json is missing. Run 'npm install' in docs/ and commit the lock file."
exit 1
fi
echo "has_docs=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::No docs/package.json found. Skipping documentation build."
echo "has_docs=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
if: steps.check.outputs.has_docs == 'true'
with:
node-version: 22
cache: npm
cache-dependency-path: docs/package-lock.json
- name: Install dependencies
if: steps.check.outputs.has_docs == 'true'
run: npm ci
working-directory: docs
- name: Build documentation
if: steps.check.outputs.has_docs == 'true'
run: npx vitepress build
working-directory: docs
- name: Upload docs artifact
if: steps.check.outputs.has_docs == 'true'
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: docs-site
path: docs/.vitepress/dist
if-no-files-found: error
release-gate:
name: Release Gate
needs: [resolve-version, build-and-test]
if: needs.resolve-version.outputs.is_release == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
environment: release
permissions:
actions: read
contents: read
steps:
- name: Log environment and reviewer status
id: release-status
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
REPO="${{ github.repository }}"
RUN_ID="${{ github.run_id }}"
ENVIRONMENT_NAME="release"
RUN_URL="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
APPROVALS_JSON="$(gh api "repos/$REPO/actions/runs/$RUN_ID/approvals" 2>/dev/null || echo "[]")"
PENDING_JSON="$(gh api "repos/$REPO/actions/runs/$RUN_ID/pending_deployments" 2>/dev/null || echo "[]")"
APPROVAL_JSON="$(echo "$APPROVALS_JSON" | jq --arg env "$ENVIRONMENT_NAME" '[.[] | select(any(.environments[]?; .name == $env))] | first // {}')"
APPROVER="$(echo "$APPROVAL_JSON" | jq -r '.user.login // "N/A"')"
APPROVAL_STATE="$(echo "$APPROVAL_JSON" | jq -r '.state // "N/A"')"
APPROVED_AT="$(echo "$APPROVAL_JSON" | jq -r '.updated_at // "N/A"')"
APPROVAL_COMMENT="$(echo "$APPROVAL_JSON" | jq -r '.comment // "N/A"')"
APPROVAL_COMMENT="${APPROVAL_COMMENT//$'\n'/ }"
APPROVAL_COMMENT="${APPROVAL_COMMENT//|/\\|}"
PENDING_COUNT="$(echo "$PENDING_JSON" | jq 'length')"
mkdir -p artifacts/release
jq -n \
--arg event "release_environment_status" \
--arg timestamp "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
--arg run_id "$RUN_ID" \
--arg run_url "$RUN_URL" \
--arg env "$ENVIRONMENT_NAME" \
--arg approval_state "$APPROVAL_STATE" \
--arg approver "$APPROVER" \
--arg approval_comment "$APPROVAL_COMMENT" \
--arg approved_at "$APPROVED_AT" \
--arg tag "${{ needs.resolve-version.outputs.tag }}" \
--arg sha "${{ needs.resolve-version.outputs.sha }}" \
--argjson pending_count "$PENDING_COUNT" \
'{
event: $event,
timestamp: $timestamp,
run_id: $run_id,
run_url: $run_url,
environment: $env,
approval: {
state: $approval_state,
reviewer: $approver,
comment: $approval_comment,
approved_at: $approved_at
},
pending_deployments_count: $pending_count,
context: {
tag: $tag,
sha: $sha
}
}' > artifacts/release/release-status.json
echo "::group::Release Environment Status"
cat artifacts/release/release-status.json
echo "::endgroup::"
{
echo "## Environment and Reviewer Status"
echo ""
echo "| Property | Value |"
echo "|----------|-------|"
echo "| **Environment** | \`$ENVIRONMENT_NAME\` |"
echo "| **Approval State** | $APPROVAL_STATE |"
echo "| **Approved By** | @$APPROVER |"
echo "| **Approved At** | $APPROVED_AT |"
echo "| **Approval Comment** | $APPROVAL_COMMENT |"
echo "| **Pending Deployments** | $PENDING_COUNT |"
echo "| **Run** | [#$RUN_ID]($RUN_URL) |"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload release status artifact
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: release-status
path: artifacts/release/release-status.json
if-no-files-found: error
release-nuget:
name: Release NuGet
needs: [resolve-version, build-and-test, release-gate]
if: needs.resolve-version.outputs.is_release == 'true' && needs.resolve-version.outputs.enable_nuget == 'true'
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.resolve-version.outputs.sha }}
- uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
with:
global-json-file: global.json
- name: Download packages
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: packages
path: artifacts/packages
- name: Mark build script executable
run: chmod +x build.sh
- name: Verify release manifest
shell: bash
run: ./build.sh ValidateReleaseManifest
- name: NuGet login (Trusted Publishing / OIDC)
if: env.NUGET_USE_OIDC == 'true'
id: nuget-login
uses: NuGet/login@d22cc5f58ff5b88bf9bd452535b4335137e24544 # v1.1.0
with:
user: ${{ vars.NUGET_USER || github.repository_owner }}
- name: Resolve NuGet API key
shell: bash
env:
OIDC_API_KEY: ${{ steps.nuget-login.outputs.NUGET_API_KEY }}
SECRET_API_KEY: ${{ secrets.NUGET_API_KEY }}
run: |
set -euo pipefail
API_KEY="$SECRET_API_KEY"
if [ "$NUGET_USE_OIDC" = "true" ]; then
API_KEY="$OIDC_API_KEY"
fi
if [ -z "$API_KEY" ]; then
echo "::error::NUGET_API_KEY is not available. Set the secret or enable OIDC."
exit 1
fi
echo "::add-mask::$API_KEY"
echo "NUGET_API_KEY=$API_KEY" >> "$GITHUB_ENV"
- name: Push packages to NuGet.org
shell: bash
run: ./build.sh PushNuGetPackages
create-release:
name: Create Release
needs: [resolve-version, release-gate, release-nuget]
if: needs.resolve-version.outputs.is_release == 'true' && (needs.release-nuget.result == 'success' || needs.release-nuget.result == 'skipped')
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: write
concurrency:
group: release-tag-${{ needs.resolve-version.outputs.tag }}
cancel-in-progress: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: ${{ needs.resolve-version.outputs.sha }}
fetch-depth: 0
- name: Download installers
if: env.ENABLE_INSTALLERS == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: artifacts/installers
pattern: installer-*
merge-multiple: true
- name: Create and push tag (idempotent)
shell: bash
run: |
set -euo pipefail
TAG="${{ needs.resolve-version.outputs.tag }}"
SHA="${{ needs.resolve-version.outputs.sha }}"
git fetch --tags --force
LOCAL_SHA="$(git rev-parse -q --verify "refs/tags/$TAG^{commit}" 2>/dev/null || true)"
if [ -n "$LOCAL_SHA" ]; then
if [ "$LOCAL_SHA" = "$SHA" ]; then
echo "::notice::Tag $TAG already exists locally at expected SHA $SHA."
exit 0
fi
echo "::error::Tag $TAG exists locally at $LOCAL_SHA but expected $SHA."
exit 1
fi
REMOTE_SHA="$(git ls-remote --tags origin "refs/tags/$TAG^{}" | awk '{print $1}' | head -n1)"
if [ -z "$REMOTE_SHA" ]; then
REMOTE_SHA="$(git ls-remote --tags origin "refs/tags/$TAG" | awk '{print $1}' | head -n1)"
fi
if [ -n "$REMOTE_SHA" ]; then
if [ "$REMOTE_SHA" = "$SHA" ]; then
echo "::notice::Tag $TAG already exists on remote at expected SHA $SHA."
exit 0
fi
echo "::error::Tag $TAG exists on remote at $REMOTE_SHA but expected $SHA."
exit 1
fi
git tag "$TAG" "$SHA"
git push origin "$TAG"
echo "Created and pushed tag $TAG at $SHA"
- name: Create or update GitHub Release
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
TAG="${{ needs.resolve-version.outputs.tag }}"
RELEASE_NAME="ChengYuan $TAG"
shopt -s nullglob
RELEASE_ASSETS=()
if [ "$ENABLE_INSTALLERS" = "true" ]; then
RELEASE_ASSETS+=(artifacts/installers/*.zip)
fi
if gh release view "$TAG" >/dev/null 2>&1; then
echo "::notice::Release already exists for $TAG. Uploading missing assets."
if [ ${#RELEASE_ASSETS[@]} -gt 0 ]; then
gh release upload "$TAG" "${RELEASE_ASSETS[@]}" --clobber
fi
exit 0
fi
if [ ${#RELEASE_ASSETS[@]} -gt 0 ]; then
gh release create "$TAG" "${RELEASE_ASSETS[@]}" \
--title "$RELEASE_NAME" \
--generate-notes
else
gh release create "$TAG" \
--title "$RELEASE_NAME" \
--generate-notes
fi
echo "Created release $TAG with ${#RELEASE_ASSETS[@]} assets"
deploy-docs:
name: Deploy Documentation
needs: [resolve-version, create-release, build-docs]
if: needs.resolve-version.outputs.is_release == 'true' && needs.build-docs.outputs.has_docs == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: false
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Check if deployment is possible
id: check
shell: bash
env:
GH_TOKEN: ${{ github.token }}
run: |
HAS_PAGES="$(gh api repos/${{ github.repository }} --jq '.has_pages' 2>/dev/null || echo "false")"
if [ "$HAS_PAGES" != "true" ]; then
echo "::notice::GitHub Pages is not enabled. Enable Settings > Pages > Source: GitHub Actions."
echo "should_deploy=false" >> "$GITHUB_OUTPUT"
else
echo "should_deploy=true" >> "$GITHUB_OUTPUT"
fi
- name: Download docs artifact
if: steps.check.outputs.should_deploy == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: docs-site
path: docs-site
- name: Upload pages artifact
if: steps.check.outputs.should_deploy == 'true'
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0
with:
path: docs-site
- name: Deploy to GitHub Pages
if: steps.check.outputs.should_deploy == 'true'
id: deployment
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0