Skip to content

WinISO.ScriptFXLib Release #6

WinISO.ScriptFXLib Release

WinISO.ScriptFXLib Release #6

# yaml-language-server: $schema=https://json.schemastore.org/github-workflow.json
# =============================================================================
# GITHUB ACTIONS WORKFLOW FOR WinISO.ScriptFXLib RELEASES
# =============================================================================
# Trigger options:
# 1. Automatically when a tag starting with "winiso-" is pushed
# (e.g. winiso-v1.00.00, winiso-v2.05.01)
# 2. Manually via GitHub Actions UI (workflow_dispatch) with custom inputs
#
# Version source of truth:
# .github/workflows/winiso-sfxlib-version.txt
# The workflow reads the version from this file first.
# If the file is missing it falls back to deriving the version from the tag.
#
# Release body template:
# .github/workflows/winiso-sfxlib-release.md
# The workflow reads this Markdown file and replaces all {{PLACEHOLDER}}
# tokens at runtime before passing the content to the release action.
name: WinISO.ScriptFXLib Release
# ---------------------------------------------------------------------------
# GLOBAL VARIABLES
# zip_basename : Base filename WITHOUT version and WITHOUT .zip extension.
# The full ZIP name is assembled dynamically in Step 2.
# repo_folder : The module subfolder inside the repository.
# temp_folder : Staging folder used during ZIP creation (never committed).
# workflow_file : Filename of this workflow (used in log output).
# version_file : Path to the plain-text file that holds the version number.
# release_md : Path to the Markdown template for the release body.
# ---------------------------------------------------------------------------
env:
zip_basename: "WinISO"
repo_folder: "WinISO.ScriptFXLib"
temp_folder: "WinISO.ScriptFXLib-Module"
workflow_file: "winiso-sfxlib-release.yml"
version_file: ".github/workflows/winiso-sfxlib-version.txt"
release_md: ".github/workflows/winiso-sfxlib-release.md"
# ---------------------------------------------------------------------------
# TRIGGER CONFIGURATION
# ---------------------------------------------------------------------------
on:
# AUTOMATIC TRIGGER - fires when a tag starting with "winiso-" is pushed
push:
tags:
- 'winiso-*'
# MANUAL TRIGGER - exposes a "Run workflow" button in the GitHub Actions UI
workflow_dispatch:
inputs:
tag_name:
description: 'Tag name for the release (e.g. winiso-v1.00.00)'
required: true
default: 'winiso-v1.00.00'
type: string
create_tag:
description: 'Create the tag if it does not exist yet'
required: true
default: true
type: boolean
unofficial_release:
description: 'Mark as Unofficial Release (for testing / debugging only)'
required: true
default: false
type: boolean
release_notes:
description: 'Additional release notes (optional)'
required: false
default: ''
type: string
# ---------------------------------------------------------------------------
# PERMISSIONS
# ---------------------------------------------------------------------------
permissions:
contents: write
actions: write
# ---------------------------------------------------------------------------
# JOBS
# ---------------------------------------------------------------------------
jobs:
release:
runs-on: ubuntu-latest
if: |
(github.event_name == 'push' && startsWith(github.ref, 'refs/tags/winiso-')) ||
github.event_name == 'workflow_dispatch'
steps:
# -----------------------------------------------------------------------
# STEP 1 - Checkout repository
# -----------------------------------------------------------------------
- name: Checkout repository
uses: actions/checkout@v4
# -----------------------------------------------------------------------
# STEP 2 - Resolve version and build dynamic variables
#
# Version priority:
# 1. winiso-sfxlib-version.txt (primary source of truth)
# 2. Tag suffix (fallback)
#
# Outputs written to $GITHUB_OUTPUT:
# tag_name - validated tag name
# version - plain version string e.g. 1.05.03
# zip_filename - full ZIP name e.g. WinISO-1.05.03-Release.zip
# release_title - full release page title
# unofficial - "true" or "false"
# -----------------------------------------------------------------------
- name: Resolve version and build dynamic variables
id: setup
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
TAG_NAME="${{ github.event.inputs.tag_name }}"
echo "Manual trigger detected"
else
TAG_NAME="${{ github.ref_name }}"
echo "Automatic tag trigger detected"
fi
echo "Tag: $TAG_NAME"
if [[ ! $TAG_NAME =~ ^winiso-.*$ ]]; then
echo "ERROR: Tag must start with 'winiso-' - provided: $TAG_NAME"
exit 1
fi
VERSION_FILE="${{ env.version_file }}"
if [ -f "$VERSION_FILE" ]; then
VERSION=$(cat "$VERSION_FILE" | tr -d '[:space:]')
echo "Version from $VERSION_FILE: $VERSION"
else
VERSION="${TAG_NAME#winiso-}"
echo "WARNING: version file not found - derived from tag: $VERSION"
fi
UNOFFICIAL="${{ github.event.inputs.unofficial_release }}"
if [ -z "$UNOFFICIAL" ]; then
UNOFFICIAL="false"
fi
if [ "$UNOFFICIAL" = "true" ]; then
FULL_ZIP="${{ env.zip_basename }}-${VERSION}-UNOFFICIAL.zip"
RELEASE_TITLE="UNOFFICIAL - WinISO.ScriptFXLib $TAG_NAME"
echo "UNOFFICIAL RELEASE mode active"
else
FULL_ZIP="${{ env.zip_basename }}-${VERSION}-Release.zip"
RELEASE_TITLE="WinISO.ScriptFXLib $TAG_NAME"
fi
echo "tag_name=$TAG_NAME" >> $GITHUB_OUTPUT
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "zip_filename=$FULL_ZIP" >> $GITHUB_OUTPUT
echo "unofficial=$UNOFFICIAL" >> $GITHUB_OUTPUT
echo "release_title=$RELEASE_TITLE" >> $GITHUB_OUTPUT
echo "--- Variable summary ---"
echo " TAG : $TAG_NAME"
echo " VERSION : $VERSION"
echo " ZIP : $FULL_ZIP"
echo " RELEASE_TITLE : $RELEASE_TITLE"
echo " UNOFFICIAL : $UNOFFICIAL"
# -----------------------------------------------------------------------
# STEP 3 - Create tag (manual trigger only)
# -----------------------------------------------------------------------
- name: Create tag if requested
if: |
github.event_name == 'workflow_dispatch' &&
github.event.inputs.create_tag == 'true'
run: |
TAG_NAME="${{ steps.setup.outputs.tag_name }}"
git config user.name "GitHub Actions Bot"
git config user.email "actions@github.com"
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
echo "Tag '$TAG_NAME' already exists - continuing"
else
git tag "$TAG_NAME"
git push origin "$TAG_NAME"
echo "Tag '$TAG_NAME' created and pushed"
fi
# -----------------------------------------------------------------------
# STEP 4 - Build release body from Markdown template
#
# Reads winiso-sfxlib-release.md, replaces all {{PLACEHOLDER}} tokens
# with runtime values and writes the result to release-body.md.
#
# Python is used for the replacements to safely handle any special
# characters, unicode or newlines that may appear in the values.
#
# IMPORTANT - YAML block scalar rule:
# Every line inside a `run: |` block must be indented at least as
# much as the first content line. Multi-line bash strings whose
# continuation lines start at column 0 would terminate the YAML
# scalar and cause a syntax error.
# Therefore UNOFFICIAL_BANNER is built with printf so the entire
# assignment stays on a single, properly indented line.
# -----------------------------------------------------------------------
- name: Build release body from template
id: build_body
run: |
OUTPUT="release-body.md"
if [ ! -f "${{ env.release_md }}" ]; then
echo "ERROR: template not found: ${{ env.release_md }}"
exit 1
fi
# Build UNOFFICIAL_BANNER with printf - single logical line,
# no column-0 continuation that would break the YAML block scalar.
if [ "${{ steps.setup.outputs.unofficial }}" = "true" ]; then
UNOFFICIAL_BANNER=$(printf '> [!CAUTION]\n> ### UNOFFICIAL RELEASE - FOR TESTING AND DEBUGGING PURPOSES ONLY\n> This release has **NOT** been officially approved. It must **NOT** be used in production environments. Use at your own risk.\n')
else
UNOFFICIAL_BANNER=""
fi
RAW_NOTES="${{ github.event.inputs.release_notes }}"
if [ -n "$RAW_NOTES" ]; then
RELEASE_NOTES_VAL="$RAW_NOTES"
else
RELEASE_NOTES_VAL="_No additional notes provided for this release._"
fi
# Write the Python renderer to a temp file.
# This avoids any heredoc / YAML indentation conflicts.
cat > /tmp/render_template.py << 'PYEOF'
import os
template_path = os.environ['TMPL']
output_path = os.environ['OUT']
replacements = {
'{{LOGO_URL}}': os.environ['LOGO_URL'],
'{{ZIP_FILENAME}}': os.environ['ZIP_FILENAME'],
'{{DOWNLOAD_URL}}': os.environ['DOWNLOAD_URL'],
'{{REPO_URL}}': os.environ['REPO_URL'],
'{{VERSION}}': os.environ['VERSION'],
'{{UNOFFICIAL_BANNER}}': os.environ['UNOFFICIAL_BANNER'],
'{{RELEASE_NOTES}}': os.environ['RELEASE_NOTES'],
}
with open(template_path, 'r', encoding='utf-8') as f:
body = f.read()
for placeholder, value in replacements.items():
body = body.replace(placeholder, value)
with open(output_path, 'w', encoding='utf-8') as f:
f.write(body)
print('Release body written to: ' + output_path)
print('Total length: ' + str(len(body)) + ' characters')
PYEOF
TMPL="${{ env.release_md }}" \
OUT="$OUTPUT" \
LOGO_URL="https://raw.githubusercontent.com/${{ github.repository }}/main/WinISO.ScriptFXLib/Ressources/WinISO.ScriptFXLib.Logo.png" \
ZIP_FILENAME="${{ steps.setup.outputs.zip_filename }}" \
DOWNLOAD_URL="https://github.com/${{ github.repository }}/releases/download/${{ steps.setup.outputs.tag_name }}/${{ steps.setup.outputs.zip_filename }}" \
REPO_URL="https://github.com/${{ github.repository }}" \
VERSION="${{ steps.setup.outputs.version }}" \
UNOFFICIAL_BANNER="$UNOFFICIAL_BANNER" \
RELEASE_NOTES="$RELEASE_NOTES_VAL" \
python3 /tmp/render_template.py
echo "body_file=$OUTPUT" >> $GITHUB_OUTPUT
echo "Release body built successfully"
# -----------------------------------------------------------------------
# STEP 5 - Create release ZIP package
#
# Only the explicitly listed files and folders are included.
# Everything else in the repository is strictly excluded.
# -----------------------------------------------------------------------
- name: Create WinISO.ScriptFXLib release package
run: |
ZIP_FILENAME="${{ steps.setup.outputs.zip_filename }}"
echo "Building: $ZIP_FILENAME"
mkdir -p "release-temp/${{ env.temp_folder }}"
if [ ! -d "${{ env.repo_folder }}" ]; then
echo "ERROR: folder '${{ env.repo_folder }}' not found in repository root"
ls -la
exit 1
fi
SRC="${{ env.repo_folder }}"
DEST="release-temp/${{ env.temp_folder }}"
for FILE in "WinISO.ScriptFXLib.psm1" "README.md" "CHANGELOG.md"; do
if [ -f "$SRC/$FILE" ]; then
cp "$SRC/$FILE" "$DEST/"
echo " Copied : $FILE"
else
echo " Skipped (not found): $FILE"
fi
done
for DIR in "Examples" "Private" "Public"; do
if [ -d "$SRC/$DIR" ]; then
cp -r "$SRC/$DIR" "$DEST/"
echo " Copied : $DIR/"
else
echo " Skipped (not found): $DIR/"
fi
done
echo "Staging folder contents:"
ls -la "$DEST/"
cd release-temp
zip -r "../${ZIP_FILENAME}" "${{ env.temp_folder }}/"
cd ..
echo "ZIP created:"
ls -lh "${ZIP_FILENAME}"
echo "ZIP contents:"
unzip -l "${ZIP_FILENAME}"
# -----------------------------------------------------------------------
# STEP 6 - Publish GitHub Release
# -----------------------------------------------------------------------
- name: Publish GitHub Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ steps.setup.outputs.tag_name }}
name: ${{ steps.setup.outputs.release_title }}
body_path: ${{ steps.build_body.outputs.body_file }}
files: ${{ steps.setup.outputs.zip_filename }}
draft: false
prerelease: ${{ steps.setup.outputs.unofficial == 'true' }}
fail_on_unmatched_files: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# -----------------------------------------------------------------------
# STEP 7 - Summary log
# -----------------------------------------------------------------------
- name: Print release summary
run: |
TAG_NAME="${{ steps.setup.outputs.tag_name }}"
ZIP_FILENAME="${{ steps.setup.outputs.zip_filename }}"
echo "======================================================"
echo " ${{ github.workflow }} - Release Summary"
echo "======================================================"
echo " Actor : ${{ github.actor }}"
echo " Tag : $TAG_NAME"
echo " Version : ${{ steps.setup.outputs.version }}"
echo " ZIP : $ZIP_FILENAME"
echo " Title : ${{ steps.setup.outputs.release_title }}"
echo " Trigger : ${{ github.event_name }}"
echo " Unofficial : ${{ steps.setup.outputs.unofficial }}"
echo " Repository : ${{ github.repository }}"
echo " Commit SHA : ${{ github.sha }}"
echo " Timestamp : $(date '+%Y-%m-%d %H:%M:%S UTC')"
echo "------------------------------------------------------"
echo " Release Page : https://github.com/${{ github.repository }}/releases/tag/$TAG_NAME"
echo " Download URL : https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/$ZIP_FILENAME"
echo "------------------------------------------------------"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo " Next run : Actions -> ${{ github.workflow }} -> Run workflow"
else
echo " Next run : push a new 'winiso-*' tag, or use manual trigger"
fi
echo " Workflow : https://github.com/${{ github.repository }}/actions/workflows/${{ env.workflow_file }}"
echo "======================================================"