mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-13 05:28:02 +03:00
4dbac421ab
* feat(advisories): add provisional ghsa feed * fix(workflows): include advisory signatures in checksums * fix(workflows): mirror ghsa feed at release root * feat(advisories): consolidate ghsa into agent feed * ci(advisories): consolidate ghsa during nvd poll * fix(advisories): retain unreplaced ghsa feed entries * chore(skills): bump advisory feed consumers * fix(release): resolve ts import closure dry run * fix(release): preserve urls while stripping comments * fix(release): ignore skill test-only changes * fix(advisories): follow ghsa pagination links * test(advisories): add nvd ghsa pipeline dry run
487 lines
21 KiB
YAML
487 lines
21 KiB
YAML
name: Deploy to GitHub Pages
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
workflow_run:
|
|
workflows: ["Skill Release"]
|
|
types: [completed]
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
pages: write
|
|
id-token: write
|
|
|
|
concurrency:
|
|
group: pages
|
|
cancel-in-progress: false
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
# Production build only: manual dispatch, push to main, or trusted release workflows.
|
|
# PR validation runs in .github/workflows/pages-verify.yml.
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(
|
|
github.event_name == 'push' &&
|
|
github.ref_name == 'main'
|
|
) ||
|
|
(
|
|
github.event_name == 'workflow_run' &&
|
|
github.event.workflow_run.conclusion == 'success' &&
|
|
github.event.workflow_run.name == 'Skill Release' &&
|
|
github.event.workflow_run.event != 'pull_request'
|
|
)
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
|
|
- name: Verify signing key consistency (repo + docs)
|
|
run: ./scripts/ci/verify_signing_key_consistency.sh
|
|
|
|
- name: Auto-discover skills from releases
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p public/skills
|
|
mkdir -p public/releases/download
|
|
|
|
echo "Fetching releases from GitHub API..."
|
|
|
|
# Helper function to download release asset by ID (works for private repos)
|
|
download_asset() {
|
|
local asset_id="$1"
|
|
local output_file="$2"
|
|
curl -fsSL \
|
|
-H "Authorization: Bearer ${GITHUB_TOKEN}" \
|
|
-H "Accept: application/octet-stream" \
|
|
"https://api.github.com/repos/${REPO}/releases/assets/${asset_id}" \
|
|
-o "$output_file"
|
|
}
|
|
export -f download_asset # Export for use in subshells (while loop)
|
|
|
|
# Fetch all releases (paginated)
|
|
RELEASES=$(gh api --paginate \
|
|
-H "Accept: application/vnd.github+json" \
|
|
"/repos/${REPO}/releases?per_page=100" \
|
|
| jq -s 'add // []')
|
|
|
|
# Start building skills index
|
|
echo '{"version":"1.0.0","updated":"'$(date -u +%Y-%m-%dT%H:%M:%SZ)'","skills":[' > public/skills/index.json
|
|
|
|
FIRST_SKILL=true
|
|
declare -A PROCESSED_SKILLS=()
|
|
|
|
# Process each release (using process substitution to avoid subshell)
|
|
while read -r release; do
|
|
TAG=$(echo "$release" | jq -r '.tag_name')
|
|
|
|
# Parse skill-name-v* pattern
|
|
if [[ "$TAG" =~ ^(.+)-v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then
|
|
SKILL_NAME="${BASH_REMATCH[1]}"
|
|
VERSION="${BASH_REMATCH[2]}"
|
|
|
|
# Skip if we already processed a newer version of this skill
|
|
if [[ -n "${PROCESSED_SKILLS[$SKILL_NAME]+x}" ]]; then
|
|
echo "Skipping older version: $TAG (already have newer)"
|
|
continue
|
|
fi
|
|
|
|
echo "Processing: $SKILL_NAME v$VERSION"
|
|
|
|
# Get skill.json asset ID from release
|
|
SKILL_JSON_ID=$(echo "$release" | jq -r '.assets[] | select(.name=="skill.json") | .id')
|
|
|
|
if [ -n "$SKILL_JSON_ID" ] && [ "$SKILL_JSON_ID" != "null" ]; then
|
|
# Basic safety checks before using tag/asset names as paths
|
|
if [[ "$TAG" == *"/"* ]] || [[ "$TAG" == *".."* ]]; then
|
|
echo " Warning: Skipping suspicious tag name: $TAG"
|
|
continue
|
|
fi
|
|
|
|
# Download skill.json first to decide whether the skill is internal
|
|
SKILL_JSON_TMP=$(mktemp)
|
|
download_asset "$SKILL_JSON_ID" "$SKILL_JSON_TMP"
|
|
|
|
# Skip internal skills (not shown in public catalog or mirrored)
|
|
IS_INTERNAL=$(jq -r '.openclaw.internal // false' "$SKILL_JSON_TMP")
|
|
if [ "$IS_INTERNAL" = "true" ]; then
|
|
echo " Skipping internal skill: $SKILL_NAME"
|
|
rm -f "$SKILL_JSON_TMP"
|
|
continue
|
|
fi
|
|
|
|
# Security: Download to temp directory first, verify signatures, then mirror to final location.
|
|
# This ensures unverified releases never appear in public/releases or the skills catalog.
|
|
|
|
# Use temp directory for downloads before verification
|
|
TEMP_DOWNLOAD_DIR=$(mktemp -d)
|
|
|
|
# Move skill.json to temp dir first
|
|
mv "$SKILL_JSON_TMP" "$TEMP_DOWNLOAD_DIR/skill.json"
|
|
|
|
# Download all remaining assets to temp dir
|
|
while read -r asset; do
|
|
ASSET_ID=$(echo "$asset" | jq -r '.id')
|
|
ASSET_NAME=$(echo "$asset" | jq -r '.name')
|
|
|
|
# Prevent path traversal / nested directories
|
|
if [[ "$ASSET_NAME" == *"/"* ]] || [[ "$ASSET_NAME" == *".."* ]]; then
|
|
echo " Warning: Skipping suspicious asset name: $ASSET_NAME"
|
|
continue
|
|
fi
|
|
|
|
# Already downloaded above
|
|
if [ "$ASSET_NAME" = "skill.json" ]; then
|
|
continue
|
|
fi
|
|
|
|
download_asset "$ASSET_ID" "$TEMP_DOWNLOAD_DIR/$ASSET_NAME"
|
|
echo " Downloaded to temp: $ASSET_NAME"
|
|
done < <(echo "$release" | jq -c '.assets[]')
|
|
|
|
# Verify signed checksums when signature artifacts are present.
|
|
# Legacy releases without signatures are still mirrored for backward compatibility.
|
|
if [ -f "$TEMP_DOWNLOAD_DIR/checksums.sig" ] && [ -f "$TEMP_DOWNLOAD_DIR/signing-public.pem" ] && [ -f "$TEMP_DOWNLOAD_DIR/checksums.json" ]; then
|
|
openssl base64 -d -A -in "$TEMP_DOWNLOAD_DIR/checksums.sig" -out "$TEMP_DOWNLOAD_DIR/checksums.sig.bin"
|
|
# Verify Ed25519 signature (requires -rawin)
|
|
if ! openssl pkeyutl -verify -rawin -pubin -inkey "$TEMP_DOWNLOAD_DIR/signing-public.pem" -sigfile "$TEMP_DOWNLOAD_DIR/checksums.sig.bin" -in "$TEMP_DOWNLOAD_DIR/checksums.json"; then
|
|
echo " Warning: Invalid checksums signature for $TAG; skipping skill"
|
|
rm -rf "$TEMP_DOWNLOAD_DIR"
|
|
continue
|
|
fi
|
|
rm -f "$TEMP_DOWNLOAD_DIR/checksums.sig.bin"
|
|
echo " Verified checksums signature"
|
|
elif [ -f "$TEMP_DOWNLOAD_DIR/checksums.json" ]; then
|
|
echo " Warning: Unsigned legacy checksums for $TAG (missing checksums.sig/signing-public.pem)"
|
|
fi
|
|
|
|
# Verification passed or skipped (legacy) - mirror to final location
|
|
MIRROR_DIR="public/releases/download/${TAG}"
|
|
mkdir -p "$MIRROR_DIR"
|
|
cp -r "$TEMP_DOWNLOAD_DIR"/* "$MIRROR_DIR"/
|
|
echo " Mirrored to: $MIRROR_DIR"
|
|
|
|
# Clean up temp directory
|
|
rm -rf "$TEMP_DOWNLOAD_DIR"
|
|
|
|
# Copy the subset needed for the site catalog (skill pages)
|
|
mkdir -p "public/skills/${SKILL_NAME}"
|
|
cp "$MIRROR_DIR/skill.json" "public/skills/${SKILL_NAME}/skill.json"
|
|
echo " Added to catalog: skill.json"
|
|
|
|
for file in checksums.json checksums.sig signing-public.pem README.md SKILL.md; do
|
|
if [ -f "$MIRROR_DIR/$file" ]; then
|
|
cp "$MIRROR_DIR/$file" "public/skills/${SKILL_NAME}/$file"
|
|
echo " Added to catalog: $file"
|
|
fi
|
|
done
|
|
|
|
# Build skill entry for index
|
|
SKILL_DATA=$(jq -c --arg tag "$TAG" '
|
|
. as $skill |
|
|
def object_or_empty($value):
|
|
if ($value | type) == "object" then $value else {} end;
|
|
def object_field($name):
|
|
object_or_empty($skill[$name]?);
|
|
def platform_meta:
|
|
($skill.platform as $platform
|
|
| if ($platform | type) == "string" then object_or_empty($skill[$platform]?)
|
|
else {}
|
|
end);
|
|
def platform_list:
|
|
([]
|
|
+ (if ($skill.platforms | type) == "array" then $skill.platforms else [] end)
|
|
+ (if ($skill.platform | type) == "string" then [$skill.platform] else [] end)
|
|
+ (["openclaw", "hermes", "nanoclaw", "picoclaw"] | map(select((object_field(.) | length) > 0))))
|
|
| map(select(type == "string") | ascii_downcase)
|
|
| unique;
|
|
{
|
|
id: .name,
|
|
name: .name,
|
|
version: .version,
|
|
description: .description,
|
|
emoji: (platform_meta.emoji // object_field("openclaw").emoji // object_field("hermes").emoji // object_field("nanoclaw").emoji // object_field("picoclaw").emoji // "📦"),
|
|
category: (platform_meta.category // object_field("openclaw").category // object_field("hermes").category // object_field("nanoclaw").category // object_field("picoclaw").category // "utility"),
|
|
platforms: platform_list,
|
|
trust: .trust.level,
|
|
tag: $tag
|
|
}
|
|
' "$MIRROR_DIR/skill.json")
|
|
|
|
# Append to index (handle first entry without comma)
|
|
if [ -f "public/skills/.first_done" ]; then
|
|
echo "," >> public/skills/index.json
|
|
else
|
|
touch "public/skills/.first_done"
|
|
fi
|
|
echo "$SKILL_DATA" >> public/skills/index.json
|
|
|
|
# Mark this skill as processed (track newest only)
|
|
PROCESSED_SKILLS["$SKILL_NAME"]=1
|
|
else
|
|
echo " Warning: skill.json not found in release assets"
|
|
fi
|
|
fi
|
|
done < <(echo "$RELEASES" | jq -c '.[]')
|
|
|
|
# Close the JSON array
|
|
echo ']}' >> public/skills/index.json
|
|
|
|
# Clean up temp file
|
|
rm -f "public/skills/.first_done"
|
|
|
|
echo ""
|
|
echo "=== Skills Index ==="
|
|
cat public/skills/index.json | jq . || cat public/skills/index.json
|
|
|
|
echo ""
|
|
echo "=== Skills Directory ==="
|
|
ls -la public/skills/
|
|
|
|
- name: Copy advisory feed to public
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p public/advisories
|
|
cp advisories/feed.json public/advisories/feed.json
|
|
if [ -f advisories/ghsa-without-cve.json ]; then
|
|
cp advisories/ghsa-without-cve.json public/advisories/ghsa-without-cve.json
|
|
fi
|
|
echo "Copied advisory feed to public/advisories/"
|
|
cat public/advisories/feed.json | jq '.advisories | length' | xargs -I {} echo "Feed contains {} advisories"
|
|
if [ -f public/advisories/ghsa-without-cve.json ]; then
|
|
cat public/advisories/ghsa-without-cve.json | jq '.advisories | length' | xargs -I {} echo "GHSA provisional feed contains {} advisories"
|
|
fi
|
|
|
|
- name: Sign advisory feed and verify
|
|
uses: ./.github/actions/sign-and-verify
|
|
with:
|
|
private_key: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY }}
|
|
private_key_passphrase: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE }}
|
|
input_file: public/advisories/feed.json
|
|
signature_file: public/advisories/feed.json.sig
|
|
public_key_output: public/signing-public.pem
|
|
|
|
- name: Sign provisional GHSA feed and verify
|
|
if: hashFiles('public/advisories/ghsa-without-cve.json') != ''
|
|
uses: ./.github/actions/sign-and-verify
|
|
with:
|
|
private_key: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY }}
|
|
private_key_passphrase: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE }}
|
|
input_file: public/advisories/ghsa-without-cve.json
|
|
signature_file: public/advisories/ghsa-without-cve.json.sig
|
|
|
|
- name: Generate advisory checksums manifest
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
FILES_JSON="{}"
|
|
ADVISORY_ARTIFACTS=(public/advisories/*.json public/advisories/*.json.sig)
|
|
for file in "${ADVISORY_ARTIFACTS[@]}"; do
|
|
[ -e "$file" ] || continue
|
|
REL_PATH="${file#public/}"
|
|
FILE_SHA=$(sha256sum "$file" | awk '{print $1}')
|
|
FILE_SIZE=$(stat -c%s "$file" 2>/dev/null || stat -f%z "$file")
|
|
FILES_JSON=$(jq \
|
|
--arg path "$REL_PATH" \
|
|
--arg sha "$FILE_SHA" \
|
|
--argjson size "$FILE_SIZE" \
|
|
'. + {($path): {sha256: $sha, size: $size, path: $path, url: ("https://clawsec.prompt.security/" + $path)}}' \
|
|
<<< "$FILES_JSON")
|
|
done
|
|
|
|
# Generate checksums manifest conforming to parseChecksumsManifest expectations:
|
|
# - schema_version: "1" (manifest format version)
|
|
# - algorithm: "sha256" (hash algorithm)
|
|
# - version: "1.1.0" (feed content version, for informational purposes)
|
|
# - generated_at, repository: metadata
|
|
# - files: map of path -> {sha256, size, path, url}
|
|
jq -n \
|
|
--arg schema_version "1" \
|
|
--arg algorithm "sha256" \
|
|
--arg version "1.1.0" \
|
|
--arg generated "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
|
--arg repo "${{ github.repository }}" \
|
|
--argjson files "$FILES_JSON" \
|
|
'{
|
|
schema_version: $schema_version,
|
|
algorithm: $algorithm,
|
|
version: $version,
|
|
generated_at: $generated,
|
|
repository: $repo,
|
|
files: $files
|
|
}' > public/checksums.json
|
|
|
|
echo "Generated public/checksums.json"
|
|
jq . public/checksums.json
|
|
|
|
- name: Sign checksums and verify
|
|
uses: ./.github/actions/sign-and-verify
|
|
with:
|
|
private_key: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY }}
|
|
private_key_passphrase: ${{ secrets.CLAWSEC_SIGNING_PRIVATE_KEY_PASSPHRASE }}
|
|
input_file: public/checksums.json
|
|
signature_file: public/checksums.sig
|
|
|
|
- name: Verify generated public signing key matches canonical key
|
|
run: |
|
|
set -euo pipefail
|
|
CANONICAL_FPR=$(openssl pkey -pubin -in clawsec-signing-public.pem -outform DER | sha256sum | awk '{print $1}')
|
|
GENERATED_FPR=$(openssl pkey -pubin -in public/signing-public.pem -outform DER | sha256sum | awk '{print $1}')
|
|
echo "Canonical key fingerprint: $CANONICAL_FPR"
|
|
echo "Generated key fingerprint: $GENERATED_FPR"
|
|
if [ "$CANONICAL_FPR" != "$GENERATED_FPR" ]; then
|
|
echo "::error::public/signing-public.pem fingerprint mismatch vs clawsec-signing-public.pem"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Copy public key to advisory directory
|
|
run: |
|
|
# Clients expect the public key at advisories/feed-signing-public.pem
|
|
mkdir -p public/advisories
|
|
cp public/signing-public.pem public/advisories/feed-signing-public.pem
|
|
echo "Public key available at:"
|
|
echo " - public/signing-public.pem (root)"
|
|
echo " - public/advisories/feed-signing-public.pem (advisory-specific)"
|
|
|
|
- name: Show signed advisory artifacts
|
|
run: |
|
|
echo "Signed advisory artifacts:"
|
|
ls -la public/advisories/*.json*
|
|
ls -la public/checksums.json public/checksums.sig public/signing-public.pem
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
|
with:
|
|
node-version: '20'
|
|
cache: 'npm'
|
|
|
|
- name: Get latest clawsec-suite release URL
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
REPO: ${{ github.repository }}
|
|
run: |
|
|
LATEST_TAG=$(
|
|
gh api --paginate \
|
|
-H "Accept: application/vnd.github+json" \
|
|
"/repos/${REPO}/releases?per_page=100" \
|
|
| jq -r -s 'add // [] | [.[] | select(.tag_name | startswith("clawsec-suite-v"))] | first | .tag_name // empty'
|
|
)
|
|
|
|
if [ -n "$LATEST_TAG" ]; then
|
|
echo "Found latest clawsec-suite tag: $LATEST_TAG"
|
|
echo "VITE_CLAWSEC_SUITE_URL=https://clawsec.prompt.security/releases/download/${LATEST_TAG}/SKILL.md" >> $GITHUB_ENV
|
|
|
|
# Create a local "latest" mirror path for clients that use GitHub-style URLs.
|
|
# This enables swapping the host:
|
|
# https://github.com/<repo>/releases/latest/download/<file>
|
|
# → https://clawsec.prompt.security/releases/latest/download/<file>
|
|
MIRROR_TAG_DIR="public/releases/download/${LATEST_TAG}"
|
|
MIRROR_LATEST_DIR="public/releases/latest/download"
|
|
rm -rf "$MIRROR_LATEST_DIR"
|
|
mkdir -p "$MIRROR_LATEST_DIR"
|
|
|
|
if [ -d "$MIRROR_TAG_DIR" ]; then
|
|
cp -f "$MIRROR_TAG_DIR"/* "$MIRROR_LATEST_DIR"/ 2>/dev/null || true
|
|
echo "Mirrored suite release assets to: $MIRROR_LATEST_DIR"
|
|
else
|
|
echo "Warning: Suite release assets not mirrored (missing: $MIRROR_TAG_DIR)"
|
|
fi
|
|
|
|
# Mirror advisories feed + signatures at the path referenced by suite docs/heartbeat
|
|
if [ -f "public/advisories/feed.json" ]; then
|
|
mkdir -p "$MIRROR_LATEST_DIR/advisories"
|
|
cp "public/advisories/feed.json" "$MIRROR_LATEST_DIR/advisories/feed.json"
|
|
cp "public/advisories/feed.json" "$MIRROR_LATEST_DIR/feed.json"
|
|
fi
|
|
if [ -f "public/advisories/feed.json.sig" ]; then
|
|
mkdir -p "$MIRROR_LATEST_DIR/advisories"
|
|
cp "public/advisories/feed.json.sig" "$MIRROR_LATEST_DIR/advisories/feed.json.sig"
|
|
cp "public/advisories/feed.json.sig" "$MIRROR_LATEST_DIR/feed.json.sig"
|
|
fi
|
|
if [ -f "public/advisories/ghsa-without-cve.json" ]; then
|
|
mkdir -p "$MIRROR_LATEST_DIR/advisories"
|
|
cp "public/advisories/ghsa-without-cve.json" "$MIRROR_LATEST_DIR/advisories/ghsa-without-cve.json"
|
|
cp "public/advisories/ghsa-without-cve.json" "$MIRROR_LATEST_DIR/ghsa-without-cve.json"
|
|
fi
|
|
if [ -f "public/advisories/ghsa-without-cve.json.sig" ]; then
|
|
mkdir -p "$MIRROR_LATEST_DIR/advisories"
|
|
cp "public/advisories/ghsa-without-cve.json.sig" "$MIRROR_LATEST_DIR/advisories/ghsa-without-cve.json.sig"
|
|
cp "public/advisories/ghsa-without-cve.json.sig" "$MIRROR_LATEST_DIR/ghsa-without-cve.json.sig"
|
|
fi
|
|
if [ -f "public/checksums.json" ]; then
|
|
cp "public/checksums.json" "$MIRROR_LATEST_DIR/checksums.json"
|
|
fi
|
|
if [ -f "public/checksums.sig" ]; then
|
|
cp "public/checksums.sig" "$MIRROR_LATEST_DIR/checksums.sig"
|
|
fi
|
|
if [ -f "public/signing-public.pem" ]; then
|
|
cp "public/signing-public.pem" "$MIRROR_LATEST_DIR/signing-public.pem"
|
|
fi
|
|
else
|
|
echo "No clawsec-suite release found, using fallback"
|
|
fi
|
|
|
|
- name: Install dependencies
|
|
run: npm ci
|
|
|
|
- name: Build
|
|
run: npm run build
|
|
env:
|
|
NODE_ENV: production
|
|
VITE_CLAWSEC_SUITE_URL: ${{ env.VITE_CLAWSEC_SUITE_URL }}
|
|
|
|
- name: Copy skills data to dist
|
|
run: |
|
|
cp -r public/skills dist/skills 2>/dev/null || echo "No skills directory"
|
|
cp public/checksums.json dist/checksums.json 2>/dev/null || echo "No checksums manifest"
|
|
cp public/checksums.sig dist/checksums.sig 2>/dev/null || echo "No checksums signature"
|
|
cp public/signing-public.pem dist/signing-public.pem 2>/dev/null || echo "No signing public key"
|
|
cp -r public/advisories dist/advisories 2>/dev/null || echo "No advisories directory"
|
|
|
|
echo "=== Dist contents ==="
|
|
ls -la dist/
|
|
ls -la dist/skills/ 2>/dev/null || echo "No skills in dist"
|
|
ls -la dist/advisories/ 2>/dev/null || echo "No advisories in dist"
|
|
|
|
- name: Add .nojekyll file
|
|
run: touch dist/.nojekyll
|
|
|
|
- name: Setup Pages
|
|
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
|
|
- name: Upload artifact
|
|
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
with:
|
|
path: ./dist
|
|
|
|
deploy:
|
|
# Deploy after a production build succeeds.
|
|
if: |
|
|
github.event_name == 'workflow_dispatch' ||
|
|
(
|
|
github.event_name == 'push' &&
|
|
github.ref_name == 'main'
|
|
) ||
|
|
(
|
|
github.event_name == 'workflow_run' &&
|
|
github.event.workflow_run.conclusion == 'success' &&
|
|
github.event.workflow_run.name == 'Skill Release' &&
|
|
github.event.workflow_run.event != 'pull_request'
|
|
)
|
|
environment:
|
|
name: github-pages
|
|
url: ${{ steps.deployment.outputs.page_url }}
|
|
runs-on: ubuntu-latest
|
|
needs: build
|
|
steps:
|
|
- name: Deploy to GitHub Pages
|
|
id: deployment
|
|
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|