WinISO.ScriptFXLib Release #6
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # 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 "======================================================" |