From 7c4519656bb96b775dd17fccebd1f06f349b7ac5 Mon Sep 17 00:00:00 2001 From: Jonathan Miller <230051081+jmiller-moko@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:22:10 -0600 Subject: [PATCH] Update build_template_zip.yml --- .github/workflows/build_template_zip.yml | 381 ++++++++--------------- 1 file changed, 127 insertions(+), 254 deletions(-) diff --git a/.github/workflows/build_template_zip.yml b/.github/workflows/build_template_zip.yml index eff826c..79fec0b 100644 --- a/.github/workflows/build_template_zip.yml +++ b/.github/workflows/build_template_zip.yml @@ -1,285 +1,158 @@ -name: Build Joomla Tenplate ZIP from src folder +name: Build Joomla Template ZIP from src folder on: - workflow_dispatch: - inputs: - target_folder: - description: "Folder to zip (relative to repo root)" - required: false - default: "src" - zip_name: - description: "Base ZIP name (default: repository name, lowercase)" - required: false - default: "" - version_suffix: - description: "Optional version suffix for filename (example 01.02.03)" - required: false - default: "" - publish_release: - description: "If true, upload ZIP and SHA256 as GitHub Release assets" - required: false - default: "false" - type: choice - options: - - "false" - - "true" - release_tag: - description: "Release tag to publish to (required if publish_release=true)" - required: false - default: "" - prerelease: - description: "If true, mark the release as prerelease" - required: false - default: "true" - type: choice - options: - - "false" - - "true" - - workflow_call: - inputs: - target_folder: - description: "Folder to zip (relative to repo root)" - required: false - type: string - default: "src" - zip_name: - description: "Base ZIP name (default: repository name, lowercase)" - required: false - type: string - default: "" - version_suffix: - description: "Optional version suffix for filename (example 01.02.03)" - required: false - type: string - default: "" - publish_release: - description: "If true, upload ZIP and SHA256 as GitHub Release assets" - required: false - type: boolean - default: false - release_tag: - description: "Release tag to publish to (required if publish_release=true)" - required: false - type: string - default: "" - prerelease: - description: "If true, mark the release as prerelease" - required: false - type: boolean - default: false + workflow_dispatch: + inputs: + attach_to_release: + description: "Attach ZIP to a GitHub Release" + required: true + default: "false" + type: choice + options: + - "false" + - "true" + release_tag: + description: "Release tag to upload to. If blank, defaults to dev/ using templateDetails.xml (example dev/03.02.00)" + required: false + default: "" permissions: - contents: write + contents: write jobs: - zip: - name: Package folder as ZIP - runs-on: ubuntu-latest + build: + name: Package template + runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v4 + env: + TEMPLATE_DIR: src/templates/moko-cassiopeia + SRC_LANGUAGE_DIR: src/language + SRC_MEDIA_DIR: src/media + OUT_DIR: dist + STAGE_DIR: dist/stage + ZIP_BASENAME: moko-cassiopeia - - name: Verify tooling - shell: bash - run: | - set -euo pipefail + steps: + - name: Checkout + uses: actions/checkout@v4 - if ! command -v zip >/dev/null 2>&1; then - echo "ERROR: zip is not installed on runner" >&2 - exit 1 - fi + - name: Build staging package and ZIP + id: pkg + shell: bash + run: | + set -euo pipefail - if ! command -v sha256sum >/dev/null 2>&1; then - echo "ERROR: sha256sum is not available on runner" >&2 - exit 1 - fi + rm -rf "${OUT_DIR}" + mkdir -p "${STAGE_DIR}" - - name: Resolve and validate inputs - shell: bash - run: | - set -euo pipefail + # Validate template source + if [ ! -f "${TEMPLATE_DIR}/templateDetails.xml" ]; then + echo "ERROR: Missing ${TEMPLATE_DIR}/templateDetails.xml" + exit 1 + fi - # Prefer workflow_call inputs when present, otherwise use workflow_dispatch inputs. - TARGET_FOLDER="${{ inputs.target_folder }}" - ZIP_NAME_INPUT="${{ inputs.zip_name }}" - VERSION_SUFFIX="${{ inputs.version_suffix }}" - PUBLISH_RELEASE="${{ inputs.publish_release }}" - RELEASE_TAG="${{ inputs.release_tag }}" - PRERELEASE="${{ inputs.prerelease }}" + # Flatten template root into stage root + cp -R "${TEMPLATE_DIR}/." "${STAGE_DIR}/" - if [[ -z "$TARGET_FOLDER" ]]; then TARGET_FOLDER="${{ github.event.inputs.target_folder }}"; fi - if [[ -z "$ZIP_NAME_INPUT" ]]; then ZIP_NAME_INPUT="${{ github.event.inputs.zip_name }}"; fi - if [[ -z "$VERSION_SUFFIX" ]]; then VERSION_SUFFIX="${{ github.event.inputs.version_suffix }}"; fi - if [[ -z "$PUBLISH_RELEASE" ]]; then PUBLISH_RELEASE="${{ github.event.inputs.publish_release }}"; fi - if [[ -z "$RELEASE_TAG" ]]; then RELEASE_TAG="${{ github.event.inputs.release_tag }}"; fi - if [[ -z "$PRERELEASE" ]]; then PRERELEASE="${{ github.event.inputs.prerelease }}"; fi + # Copy language and media to stage root if present + if [ -d "${SRC_LANGUAGE_DIR}" ]; then + cp -R "${SRC_LANGUAGE_DIR}" "${STAGE_DIR}/language" + fi - TARGET_FOLDER="${TARGET_FOLDER//[[:space:]]/}" - ZIP_NAME_INPUT="${ZIP_NAME_INPUT//[[:space:]]/}" - VERSION_SUFFIX="${VERSION_SUFFIX//[[:space:]]/}" - RELEASE_TAG="${RELEASE_TAG//[[:space:]]/}" + if [ -d "${SRC_MEDIA_DIR}" ]; then + cp -R "${SRC_MEDIA_DIR}" "${STAGE_DIR}/media" + fi - if [[ -z "$TARGET_FOLDER" ]]; then - TARGET_FOLDER="src" - fi + # Hard checks for installer-critical items + test -f "${STAGE_DIR}/templateDetails.xml" + test -f "${STAGE_DIR}/index.php" + test -f "${STAGE_DIR}/component.php" - if [[ "$PUBLISH_RELEASE" == "true" || "$PUBLISH_RELEASE" == "True" ]]; then - PUBLISH_RELEASE="true" - else - PUBLISH_RELEASE="false" - fi + # Read version from manifest + VERSION="$(python - << 'PY' + import xml.etree.ElementTree as ET + root = ET.parse("dist/stage/templateDetails.xml").getroot() + v = root.findtext("version") or "" + v = v.strip() + if not v: + raise SystemExit("ERROR: not found in templateDetails.xml") + print(v) + PY + )" - if [[ "$PRERELEASE" == "true" || "$PRERELEASE" == "True" ]]; then - PRERELEASE="true" - else - PRERELEASE="false" - fi + ZIP_NAME="${ZIP_BASENAME}-${VERSION}.zip" + ZIP_PATH="${OUT_DIR}/${ZIP_NAME}" - if [[ ! -d "$TARGET_FOLDER" ]]; then - echo "ERROR: Folder does not exist: $TARGET_FOLDER" >&2 - exit 1 - fi + (cd "${STAGE_DIR}" && zip -r "../${ZIP_NAME}" .) - if [[ -n "$VERSION_SUFFIX" ]] && [[ ! "$VERSION_SUFFIX" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]]; then - echo "ERROR: version_suffix must match NN.NN.NN (example 01.02.03)" >&2 - exit 1 - fi + echo "version=${VERSION}" >> "${GITHUB_OUTPUT}" + echo "zip_name=${ZIP_NAME}" >> "${GITHUB_OUTPUT}" + echo "zip_path=${ZIP_PATH}" >> "${GITHUB_OUTPUT}" - if [[ -n "$ZIP_NAME_INPUT" ]]; then - ZIP_NAME_INPUT="$(echo "$ZIP_NAME_INPUT" | tr '[:upper:]' '[:lower:]')" - fi + - name: Compute effective release tag + id: tag + shell: bash + run: | + set -euo pipefail - if [[ "$PUBLISH_RELEASE" == "true" ]] && [[ -z "$RELEASE_TAG" ]]; then - echo "ERROR: release_tag is required when publish_release=true" >&2 - exit 1 - fi + INPUT_TAG="${{ github.event.inputs.release_tag }}" + INPUT_TAG="$(echo "${INPUT_TAG}" | tr -d '[:space:]')" - echo "TARGET_FOLDER=$TARGET_FOLDER" >> "$GITHUB_ENV" - echo "INPUT_ZIP_NAME=$ZIP_NAME_INPUT" >> "$GITHUB_ENV" - echo "INPUT_VERSION_SUFFIX=$VERSION_SUFFIX" >> "$GITHUB_ENV" - echo "PUBLISH_RELEASE=$PUBLISH_RELEASE" >> "$GITHUB_ENV" - echo "RELEASE_TAG=$RELEASE_TAG" >> "$GITHUB_ENV" - echo "PRERELEASE=$PRERELEASE" >> "$GITHUB_ENV" + if [ -n "${INPUT_TAG}" ]; then + EFFECTIVE_TAG="${INPUT_TAG}" + else + EFFECTIVE_TAG="dev/${{ steps.pkg.outputs.version }}" + fi - - name: Prepare dist folder - shell: bash - run: | - set -euo pipefail - rm -rf dist - mkdir -p dist + echo "effective_tag=${EFFECTIVE_TAG}" >> "${GITHUB_OUTPUT}" - - name: Resolve version - shell: bash - run: | - set -euo pipefail + - name: Upload workflow artifact (temporary) + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.pkg.outputs.zip_name }} + path: ${{ steps.pkg.outputs.zip_path }} + retention-days: 30 - VERSION_SUFFIX="$INPUT_VERSION_SUFFIX" + - name: Attach ZIP to GitHub Release (optional) + if: ${{ github.event.inputs.attach_to_release == 'true' }} + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + shell: bash + run: | + set -euo pipefail - if [[ -z "$VERSION_SUFFIX" ]]; then - XML_FILE="" - if [[ -f "$TARGET_FOLDER/templateDetails.xml" ]]; then - XML_FILE="$TARGET_FOLDER/templateDetails.xml" - elif [[ -f "$TARGET_FOLDER/manifest.xml" ]]; then - XML_FILE="$TARGET_FOLDER/manifest.xml" - fi + TAG="${{ steps.tag.outputs.effective_tag }}" + ZIP="${{ steps.pkg.outputs.zip_path }}" - if [[ -n "$XML_FILE" ]]; then - VERSION_SUFFIX=$(grep -oPm1 '(?<=)[^<]+' "$XML_FILE" || true) - VERSION_SUFFIX="${VERSION_SUFFIX//[[:space:]]/}" - fi - fi + if [ -z "${TAG}" ]; then + echo "ERROR: release tag is empty." + exit 1 + fi - if [[ -n "$VERSION_SUFFIX" ]] && [[ ! "$VERSION_SUFFIX" =~ ^[0-9]{2}\.[0-9]{2}\.[0-9]{2}$ ]]; then - echo "ERROR: Version found in XML is invalid: $VERSION_SUFFIX" >&2 - exit 1 - fi + # Ensure the tag exists locally for release creation (optional) + # GitHub will still allow creating a release for a tag name, but tagging is best practice. + if ! git rev-parse -q --verify "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "INFO: Tag ${TAG} not found locally. Creating lightweight tag on current commit." + git tag "${TAG}" + git push origin "${TAG}" + fi - if [[ -z "$VERSION_SUFFIX" ]]; then - VERSION_SUFFIX="$(echo "${GITHUB_REPOSITORY##*/}" | tr '[:upper:]' '[:lower:]')" - fi + # Create release if missing, then upload asset with clobber + if ! gh release view "${TAG}" >/dev/null 2>&1; then + gh release create "${TAG}" \ + --title "${TAG}" \ + --notes "Automated build artifact upload." \ + --draft + fi - echo "VERSION_SUFFIX=$VERSION_SUFFIX" >> "$GITHUB_ENV" + # Upload asset + set +e + gh release upload "${TAG}" "${ZIP}" --clobber + RC=$? + set -e - - name: Create ZIP archive and SHA256 - shell: bash - run: | - set -euo pipefail - - DEFAULT_REPO_NAME="$(echo "${GITHUB_REPOSITORY##*/}" | tr '[:upper:]' '[:lower:]')" - BASE_ZIP_NAME="$INPUT_ZIP_NAME" - - if [[ -z "$BASE_ZIP_NAME" ]]; then - BASE_ZIP_NAME="$DEFAULT_REPO_NAME" - fi - - SAFE_FOLDER_NAME="$(echo "$TARGET_FOLDER" | tr '/' '-')" - - ZIP_NAME="${BASE_ZIP_NAME}-${SAFE_FOLDER_NAME}-${VERSION_SUFFIX}.zip" - ZIP_PATH="dist/$ZIP_NAME" - - cd "$TARGET_FOLDER" - - zip -r "../$ZIP_PATH" . \ - -x "*.DS_Store" \ - -x "__MACOSX/*" \ - -x "node_modules/*" \ - -x "coverage/*" \ - -x "*.log" \ - -x "*.tmp" - - cd - >/dev/null 2>&1 - - SHA_PATH="${ZIP_PATH}.sha256" - sha256sum "$ZIP_PATH" | awk '{print $1}' > "$SHA_PATH" - - ZIP_ARTIFACT_NAME="${ZIP_NAME}" - echo "ZIP_NAME=$ZIP_NAME" >> "$GITHUB_ENV" - echo "ZIP_ARTIFACT_NAME=$ZIP_ARTIFACT_NAME" >> "$GITHUB_ENV" - echo "ZIP_PATH=$ZIP_PATH" >> "$GITHUB_ENV" - echo "SHA_PATH=$SHA_PATH" >> "$GITHUB_ENV" - - - name: Export artifact metadata - id: meta - shell: bash - run: | - set -euo pipefail - echo "zip_artifact_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" - echo "sha_artifact_name=${ZIP_NAME}.sha256" >> "$GITHUB_OUTPUT" - echo "zip_file_name=$ZIP_NAME" >> "$GITHUB_OUTPUT" - echo "sha_file_name=$(basename "$SHA_PATH")" >> "$GITHUB_OUTPUT" - - - name: Upload ZIP artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ env.ZIP_ARTIFACT_NAME }} - path: ${{ env.ZIP_PATH }} - - - name: Upload SHA256 artifact - uses: actions/upload-artifact@v4 - with: - name: ${{ env.ZIP_NAME }}.sha256 - path: ${{ env.SHA_PATH }} - - - name: Upload assets to GitHub Release - id: release_upload - if: ${{ env.PUBLISH_RELEASE == 'true' }} - uses: softprops/action-gh-release@v2 - with: - tag_name: ${{ env.RELEASE_TAG }} - prerelease: ${{ env.PRERELEASE == 'true' }} - files: | - ${{ env.ZIP_PATH }} - ${{ env.SHA_PATH }} - - - name: Export release upload status - id: release_status - shell: bash - run: | - set -euo pipefail - if [[ "$PUBLISH_RELEASE" == "true" ]]; then - echo "release_uploaded=true" >> "$GITHUB_OUTPUT" - else - echo "release_uploaded=false" >> "$GITHUB_OUTPUT" - fi + if [ $RC -ne 0 ]; then + echo "ERROR: Failed to upload asset to release ${TAG}." + echo "NOTE: GitHub may reject uploads to an immutable release. If so, create a new draft release or a new tag and retry." + exit $RC + fi