goldenChat base source add
This commit is contained in:
+323
@@ -0,0 +1,323 @@
|
||||
name: Create GitHub Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "*.*.*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag to publish (e.g. 0.22.0)"
|
||||
required: true
|
||||
type: string
|
||||
dryRun:
|
||||
description: "Run in dry-run mode to preview release creation without publishing."
|
||||
required: false
|
||||
default: true
|
||||
type: boolean
|
||||
|
||||
concurrency:
|
||||
group: github-release-${{ github.repository }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
create_release:
|
||||
name: Create GitHub Release
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
GH_TA4J_REPO_TOKEN: ${{ secrets.GH_TA4J_REPO_TOKEN }}
|
||||
|
||||
steps:
|
||||
- name: Determine checkout ref
|
||||
id: checkout_ref
|
||||
run: |
|
||||
INPUT_TAG="${{ github.event.inputs.tag }}"
|
||||
if [[ -n "$INPUT_TAG" ]]; then
|
||||
# For workflow_dispatch, construct tag ref from input
|
||||
if [[ "$INPUT_TAG" == refs/tags/* ]]; then
|
||||
INPUT_TAG="${INPUT_TAG#refs/tags/}"
|
||||
fi
|
||||
CHECKOUT_REF="refs/tags/${INPUT_TAG}"
|
||||
echo "tag=${INPUT_TAG}" >> $GITHUB_OUTPUT
|
||||
else
|
||||
# For tag push events, use the ref directly
|
||||
CHECKOUT_REF="${GITHUB_REF}"
|
||||
fi
|
||||
echo "ref=${CHECKOUT_REF}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Verify GH_TA4J_REPO_TOKEN present
|
||||
run: |
|
||||
if [[ -z "${GH_TA4J_REPO_TOKEN:-}" ]]; then
|
||||
echo "::error::GH_TA4J_REPO_TOKEN is required to create GitHub Releases."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Checkout full history
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ steps.checkout_ref.outputs.ref }}
|
||||
token: ${{ env.GH_TA4J_REPO_TOKEN }}
|
||||
|
||||
- name: Checkout workflow support files
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 1
|
||||
ref: ${{ github.sha }}
|
||||
path: workflow-support
|
||||
token: ${{ env.GH_TA4J_REPO_TOKEN }}
|
||||
|
||||
- name: Normalize dry run input
|
||||
id: dry_run
|
||||
run: |
|
||||
if [ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]; then
|
||||
raw="${{ github.event.inputs.dryRun }}"
|
||||
if [ -z "$raw" ]; then
|
||||
raw=true
|
||||
fi
|
||||
normalized=$(printf '%s' "$raw" | tr '[:upper:]' '[:lower:]')
|
||||
|
||||
if [ "$normalized" = "false" ] || [ "$normalized" = "0" ] || [ "$normalized" = "no" ]; then
|
||||
dry_run=false
|
||||
else
|
||||
dry_run=true
|
||||
fi
|
||||
else
|
||||
raw=""
|
||||
dry_run=false
|
||||
fi
|
||||
|
||||
echo "audit:dry_run_input_raw='${raw}'"
|
||||
echo "audit:dry_run_normalized=$dry_run"
|
||||
echo "dryRun=$dry_run" >> $GITHUB_OUTPUT
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Determine Tag + Validate Release Notes File
|
||||
# ------------------------------------------------------------
|
||||
- name: Determine Version
|
||||
id: version
|
||||
run: |
|
||||
echo "::group::Resolve GitHub Release tag and notes"
|
||||
INPUT_TAG="${{ steps.checkout_ref.outputs.tag }}"
|
||||
if [[ -n "$INPUT_TAG" ]]; then
|
||||
TAG="$INPUT_TAG"
|
||||
else
|
||||
if [[ "${GITHUB_EVENT_NAME}" = "workflow_dispatch" ]]; then
|
||||
echo "Error: 'tag' input is required for workflow_dispatch runs." >&2
|
||||
exit 1
|
||||
fi
|
||||
TAG="${GITHUB_REF##*/}"
|
||||
fi
|
||||
if [[ ! "$TAG" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::error::GitHub Release tags must be bare SemVer major.minor.patch values. Got: $TAG"
|
||||
exit 1
|
||||
fi
|
||||
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||
NOTES_FILE="release/${TAG}.md"
|
||||
if [[ ! -f "$NOTES_FILE" ]]; then
|
||||
echo "Release notes file $NOTES_FILE not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "notes_file=$NOTES_FILE" >> $GITHUB_OUTPUT
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Preflight release permissions (dry run)
|
||||
if: steps.dry_run.outputs.dryRun == 'true'
|
||||
id: token_preflight
|
||||
env:
|
||||
GH_TA4J_REPO_TOKEN: ${{ env.GH_TA4J_REPO_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
python - <<'PY'
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import urllib.request
|
||||
|
||||
owner = os.environ.get("GITHUB_REPOSITORY_OWNER")
|
||||
repo = os.environ.get("GITHUB_REPOSITORY", "").split("/", 1)[-1]
|
||||
if not owner or not repo:
|
||||
print("::error::Unable to resolve repository context for permission preflight.")
|
||||
sys.exit(1)
|
||||
|
||||
def viewer_permission(token: str) -> str:
|
||||
query = {
|
||||
"query": "query($owner:String!,$repo:String!){repository(owner:$owner,name:$repo){viewerPermission}}",
|
||||
"variables": {"owner": owner, "repo": repo},
|
||||
}
|
||||
req = urllib.request.Request(
|
||||
"https://api.github.com/graphql",
|
||||
data=json.dumps(query).encode("utf-8"),
|
||||
headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
"User-Agent": "ta4j-release-preflight",
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
data = json.load(resp)
|
||||
if "errors" in data:
|
||||
raise RuntimeError(data["errors"])
|
||||
return data["data"]["repository"]["viewerPermission"]
|
||||
|
||||
def has_write(permission: str) -> bool:
|
||||
return permission in ("WRITE", "MAINTAIN", "ADMIN")
|
||||
|
||||
def check_token(token, label):
|
||||
if not token:
|
||||
print(f"{label}:missing")
|
||||
return None
|
||||
try:
|
||||
permission = viewer_permission(token)
|
||||
print(f"{label}:{permission}")
|
||||
return permission
|
||||
except Exception as exc:
|
||||
print(f"::warning::{label} permission check failed: {exc}")
|
||||
return None
|
||||
|
||||
pat = os.environ.get("GH_TA4J_REPO_TOKEN", "")
|
||||
|
||||
pat_perm = check_token(pat, "GH_TA4J_REPO_TOKEN")
|
||||
|
||||
if not (pat_perm and has_write(pat_perm)):
|
||||
print("::error::GH_TA4J_REPO_TOKEN lacks write permission to create a release.")
|
||||
sys.exit(1)
|
||||
|
||||
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as fh:
|
||||
fh.write(f"repo_token_permission={pat_perm}\n")
|
||||
PY
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Prepare Release Notes Body
|
||||
# ------------------------------------------------------------
|
||||
# ------------------------------------------------------------
|
||||
# Build artifacts: core, examples, ALL modules
|
||||
# Produces:
|
||||
# - *.jar
|
||||
# - *-javadoc.jar
|
||||
# - *-sources.jar
|
||||
# ------------------------------------------------------------
|
||||
- name: Set up JDK 25
|
||||
uses: actions/setup-java@v5
|
||||
with:
|
||||
distribution: temurin
|
||||
java-version: 25
|
||||
cache: maven
|
||||
|
||||
- name: Build all module artifacts
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Build GitHub Release artifacts"
|
||||
max_attempts=3
|
||||
attempt=1
|
||||
|
||||
while true; do
|
||||
echo "audit:release_artifact_build attempt=${attempt}/${max_attempts}"
|
||||
if mvn -B -DskipTests -Dgpg.skip=true -Pproduction-release package 2>&1 | tee release-artifact-build.log; then
|
||||
break
|
||||
fi
|
||||
|
||||
if ((attempt >= max_attempts)); then
|
||||
echo "::error::Release artifact build failed after ${max_attempts} attempts."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
backoff_seconds=$((attempt * 15))
|
||||
echo "::warning::Release artifact build failed on attempt ${attempt}. Retrying in ${backoff_seconds}s."
|
||||
sleep "${backoff_seconds}"
|
||||
attempt=$((attempt + 1))
|
||||
done
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Validate release artifacts
|
||||
id: artifacts
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Validate exact GitHub Release artifact manifest"
|
||||
python3 workflow-support/scripts/release/release_helpers.py artifact-manifest \
|
||||
--version "${{ steps.version.outputs.tag }}" \
|
||||
--output artifact-manifest.txt \
|
||||
--github-output "$GITHUB_OUTPUT" \
|
||||
--strict
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Dry-run summary
|
||||
if: steps.dry_run.outputs.dryRun == 'true'
|
||||
run: |
|
||||
echo "DRY RUN ENABLED: build completed; release publish skipped."
|
||||
echo "Tag: ${{ steps.version.outputs.tag }}"
|
||||
echo "Release notes: ${{ steps.version.outputs.notes_file }}"
|
||||
echo "GH_TA4J_REPO_TOKEN permission: ${{ steps.token_preflight.outputs.repo_token_permission }}"
|
||||
echo "Artifacts:"
|
||||
files="${{ steps.artifacts.outputs.files }}"
|
||||
if [[ -z "${files}" ]]; then
|
||||
echo " (none found)"
|
||||
else
|
||||
while IFS= read -r file; do
|
||||
echo " - ${file#./}"
|
||||
done <<< "${files}"
|
||||
fi
|
||||
|
||||
# ------------------------------------------------------------
|
||||
# Upload full artifact set to GitHub Release
|
||||
# ------------------------------------------------------------
|
||||
- name: Create GitHub Release
|
||||
if: steps.dry_run.outputs.dryRun != 'true'
|
||||
uses: softprops/action-gh-release@v3
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
body_path: ${{ steps.version.outputs.notes_file }}
|
||||
files: ${{ steps.artifacts.outputs.files }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ env.GH_TA4J_REPO_TOKEN }}
|
||||
|
||||
- name: GitHub Release summary
|
||||
if: always()
|
||||
env:
|
||||
DRY_RUN: ${{ steps.dry_run.outputs.dryRun }}
|
||||
TAG: ${{ steps.version.outputs.tag }}
|
||||
NOTES_FILE: ${{ steps.version.outputs.notes_file }}
|
||||
run: |
|
||||
if [ "${DRY_RUN:-false}" = "true" ]; then
|
||||
mutation_plan="would create/update the GitHub Release and upload artifacts for ${TAG:-unknown}"
|
||||
rerun_guidance="rerun github-release.yml with dryRun=false for this tag, or let the tag push path run"
|
||||
else
|
||||
mutation_plan="completed GitHub Release publication"
|
||||
rerun_guidance="not needed for this non-dry-run GitHub Release run"
|
||||
fi
|
||||
cat > github-release-audit.json <<EOF
|
||||
{
|
||||
"dryRun": "${DRY_RUN:-}",
|
||||
"tag": "${TAG:-}",
|
||||
"notesFile": "${NOTES_FILE:-}",
|
||||
"artifactManifest": "artifact-manifest.txt",
|
||||
"mutationPlan": "${mutation_plan}",
|
||||
"rerunGuidance": "${rerun_guidance}",
|
||||
"runUrl": "https://github.com/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
|
||||
}
|
||||
EOF
|
||||
{
|
||||
echo "## GitHub Release"
|
||||
echo ""
|
||||
echo "- dry run: ${DRY_RUN:-unknown}"
|
||||
echo "- tag: ${TAG:-unknown}"
|
||||
echo "- notes file: ${NOTES_FILE:-unknown}"
|
||||
echo "- artifact manifest: artifact-manifest.txt"
|
||||
echo "- mutation plan: ${mutation_plan}"
|
||||
echo "- rerun guidance: ${rerun_guidance}"
|
||||
} >> "$GITHUB_STEP_SUMMARY"
|
||||
|
||||
- name: Upload GitHub Release audit artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: github-release-audit-${{ github.run_id }}
|
||||
path: |
|
||||
github-release-audit.json
|
||||
artifact-manifest.txt
|
||||
release-artifact-build.log
|
||||
if-no-files-found: ignore
|
||||
retention-days: 14
|
||||
Reference in New Issue
Block a user