diff --git a/.github/workflows/link_requirement.yml b/.github/workflows/link_requirement.yml index c80f6e89..94c2ce1b 100644 --- a/.github/workflows/link_requirement.yml +++ b/.github/workflows/link_requirement.yml @@ -7,14 +7,6 @@ on: issues: types: [opened, edited] - # Manual test trigger - workflow_dispatch: - inputs: - issue_number: - required: true - type: number - description: "Issue number to test with" - permissions: contents: read issues: write @@ -23,9 +15,8 @@ permissions: jobs: link-requirement: - # Run for Feature Issue Type OR workflow_dispatch test mode + # Run only for Issue Type = Feature if: > - github.event_name == 'workflow_dispatch' || github.event.issue.type == 'Feature' || github.event.issue.type.name == 'Feature' @@ -33,25 +24,13 @@ jobs: env: DEFAULT_REQUIREMENTS_REPO: ${{ vars.DEFAULT_REQUIREMENTS_REPO || 'dmf-mxl/mxl-requirements' }} - ISSUE_NUMBER: ${{ github.event.issue.number || inputs.issue_number }} + ISSUE_NUMBER: ${{ github.event.issue.number }} steps: ########################################################################### - # 0. TOKEN INITIALIZATION — MUST RUN BEFORE ANY gh COMMANDS + # 0) TOKEN: Use org GitHub App token BEFORE any gh commands ########################################################################### - - name: Determine token mode - id: token_mode - run: | - echo "Event: ${{ github.event_name }}" - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "mode=dispatch" >> "$GITHUB_OUTPUT" - else - echo "mode=real" >> "$GITHUB_OUTPUT" - fi - - # Real (issues) → GitHub App token - - name: Create GitHub App token - if: ${{ steps.token_mode.outputs.mode == 'real' }} + - name: Create GitHub App token (org) id: app_token uses: actions/create-github-app-token@v2 with: @@ -59,17 +38,10 @@ jobs: private-key: ${{ secrets.CROSS_REPO_ISSUE_ACCESS_PRIVATE_KEY }} owner: dmf-mxl - - name: Export GH_TOKEN (real) - if: ${{ steps.token_mode.outputs.mode == 'real' }} + - name: Export GH_TOKEN run: | echo "GH_TOKEN=${{ steps.app_token.outputs.token }}" >> "$GITHUB_ENV" - # Test mode → builtin GITHUB_TOKEN - - name: Export GH_TOKEN (dispatch) - if: ${{ steps.token_mode.outputs.mode == 'dispatch' }} - run: | - echo "GH_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> "$GITHUB_ENV" - - name: Verify GH_TOKEN run: | if [ -z "$GH_TOKEN" ]; then @@ -79,133 +51,106 @@ jobs: echo "GH_TOKEN OK" ########################################################################### - # 1. FETCH ISSUE BODY — STORED MULTILINE INTO ISSUE_BODY + # 1) FETCH ISSUE BODY (as rendered markdown) ########################################################################### - name: Fetch issue body - id: fetch + id: fetch_body run: | - echo "Fetching body for issue #${ISSUE_NUMBER}" BODY=$(gh api "repos/${{ github.repository }}/issues/${ISSUE_NUMBER}" --jq .body || echo "") - - # Save to env var ISSUE_BODY (multiline-safe) { echo "ISSUE_BODY<> "$GITHUB_ENV" - - name: DEBUG — Show first 300 chars of ISSUE_BODY - run: | - echo "--- ISSUE_BODY (truncated) ---" - echo "$ISSUE_BODY" | head -c 300 - echo - echo "------------------------------" - - ########################################################################### - # 2. Extract requirement_link from plain markdown (NO PARSER!) + # 2) EXTRACT requirement_link from the rendered markdown (robust) + # Looks for a line after any heading containing "Requirement Link" ########################################################################### - - name: Extract requirement_link from markdown + - name: Extract requirement_link from body id: extract_raw run: | - echo "=== Extracting requirement_link from ISSUE_BODY ===" - - # Find the line after the heading - REQ_LINE=$(printf "%s\n" "$ISSUE_BODY" | awk ' - /^### Requirement Link/ {getline; print} - ' | xargs) + # Remove CRs and non-printables, collapse whitespace + CLEAN=$(printf "%s\n" "$ISSUE_BODY" \ + | sed 's/\r//g' \ + | sed 's/[^[:print:]\t]//g' \ + | sed 's/[[:space:]]\+/ /g') - echo "Extracted requirement_link='$REQ_LINE'" + # Take the line AFTER a heading containing "Requirement Link" (case/space-insensitive) + REQ_LINE=$(printf "%s\n" "$CLEAN" \ + | awk 'tolower($0) ~ /requirement[[:space:]]*link/ {getline; print}' \ + | xargs) echo "requirement_link=$REQ_LINE" >> "$GITHUB_OUTPUT" - ########################################################################### - # 3. Guard requirement_link + # 3) GUARD ########################################################################### - name: Guard requirement_link exists id: guard run: | LINK="${{ steps.extract_raw.outputs.requirement_link }}" - echo "LINK='$LINK'" - if [ -z "$LINK" ]; then - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then - echo "Test mode: continuing without requirement_link" - echo "has=true" >> "$GITHUB_OUTPUT" - else - echo "No requirement_link found — skipping" - echo "has=false" >> "$GITHUB_OUTPUT" - fi + echo "No requirement_link found — skipping run." + echo "has=false" >> "$GITHUB_OUTPUT" else - echo "OK: requirement_link found" + echo "Found requirement_link: '$LINK'" echo "has=true" >> "$GITHUB_OUTPUT" + fi ########################################################################### - # 4. Normalize requirement_link + # 4) NORMALIZE requirement_link → owner_repo, issue_num, requirement_url ########################################################################### - name: Normalize requirement_link if: ${{ steps.guard.outputs.has == 'true' }} - id: extract + id: normalize env: RAW_LINK: ${{ steps.extract_raw.outputs.requirement_link }} DEFAULT: ${{ env.DEFAULT_REQUIREMENTS_REPO }} run: | set -euo pipefail - echo "Normalising '$RAW_LINK'" LINK="$(echo "$RAW_LINK" | xargs)" OWNER_REPO="" ISSUE_NUM="" - FULL_URL="" if [[ "$LINK" =~ ^[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+#[0-9]+$ ]]; then OWNER_REPO="${LINK%%#*}" ISSUE_NUM="${LINK##*#}" - elif [[ "$LINK" =~ ^\#?[0-9]+$ ]]; then OWNER_REPO="$DEFAULT" ISSUE_NUM="${LINK#\#}" - elif [[ "$LINK" =~ ^https?://github\.com/([^/]+)/([^/]+)/issues/([0-9]+) ]]; then OWNER_REPO="${BASH_REMATCH[1]}/${BASH_REMATCH[2]}" ISSUE_NUM="${BASH_REMATCH[3]}" + fi + if [[ -z "$OWNER_REPO" || -z "$ISSUE_NUM" ]]; then + echo "Could not normalize requirement_link='$LINK' — skipping." + exit 0 fi FULL_URL="https://github.com/${OWNER_REPO}/issues/${ISSUE_NUM}" - echo "owner_repo=$OWNER_REPO" >> "$GITHUB_OUTPUT" echo "issue_num=$ISSUE_NUM" >> "$GITHUB_OUTPUT" echo "requirement_url=$FULL_URL" >> "$GITHUB_OUTPUT" - - name: DEBUG — Normalisation - run: | - echo "owner_repo=${{ steps.extract.outputs.owner_repo }}" - echo "issue_num=${{ steps.extract.outputs.issue_num }}" - echo "requirement_url=${{ steps.extract.outputs.requirement_url }}" - - ########################################################################### - # 5. Create parent → child sub-issue link + # 5) SUB-ISSUE LINK: parent = requirement, child = feature ########################################################################### - name: Link as sub-issue - if: ${{ steps.extract.outputs.issue_num != '' }} + if: ${{ steps.normalize.outputs.issue_num != '' }} env: - PARENT_REPO: ${{ steps.extract.outputs.owner_repo }} - PARENT_NUMBER: ${{ steps.extract.outputs.issue_num }} + PARENT_REPO: ${{ steps.normalize.outputs.owner_repo }} + PARENT_NUMBER: ${{ steps.normalize.outputs.issue_num }} CHILD_REPO: ${{ github.repository }} CHILD_NUMBER: ${{ env.ISSUE_NUMBER }} run: | set -euo pipefail - echo "Linking child #$CHILD_NUMBER → parent #$PARENT_NUMBER" PARENT_NODE=$(gh issue view "https://github.com/$PARENT_REPO/issues/$PARENT_NUMBER" --json id --jq .id) CHILD_NODE=$(gh issue view "https://github.com/$CHILD_REPO/issues/$CHILD_NUMBER" --json id --jq .id) - echo "PARENT_NODE=$PARENT_NODE" - echo "CHILD_NODE=$CHILD_NODE" - gh api graphql \ -H "GraphQL-Features: sub_issues" \ -f query=" @@ -220,26 +165,23 @@ jobs: } " - ########################################################################### - # 6. Append clickable requirement URL to issue body + # 6) APPEND CLICKABLE URL TO END OF ISSUE BODY (idempotent) ########################################################################### - name: Append requirement URL to issue body - if: ${{ steps.extract.outputs.issue_num != '' }} + if: ${{ steps.normalize.outputs.issue_num != '' }} env: - REQUIRE_URL: ${{ steps.extract.outputs.requirement_url }} + REQUIRE_URL: ${{ steps.normalize.outputs.requirement_url }} CHILD_REPO: ${{ github.repository }} CHILD_NUMBER: ${{ env.ISSUE_NUMBER }} run: | - echo "Appending requirement URL to issue #${CHILD_NUMBER}" - - gh api "repos/$CHILD_REPO/issues/$CHILD_NUMBER" --jq .body > body_current.txt - - sed -e '//,//d' \ - body_current.txt > body_clean.txt + set -euo pipefail - printf "\n\n\n%s\n\n" "$REQUIRE_URL" \ - >> body_clean.txt + gh api "repos/$CHILD_REPO/issues/$CHILD_NUMBER" --jq .body > body_current.txt || echo -n > body_current.txt + # remove previous block + sed -e '//,//d' body_current.txt > body_clean.txt + # append new block + printf "\n\n\n%s\n\n" "$REQUIRE_URL" >> body_clean.txt NEW_BODY="$(cat body_clean.txt)" @@ -247,17 +189,4 @@ jobs: --method PATCH \ -H "Accept: application/vnd.github+json" \ "repos/$CHILD_REPO/issues/$CHILD_NUMBER" \ - -f body="$NEW_BODY" - - - ########################################################################### - # 7. Summary - ########################################################################### - - name: DEBUG — Final summary - run: | - echo "Event: ${{ github.event_name }}" - echo "Issue Number: ${{ env.ISSUE_NUMBER }}" - echo "Owner Repo: ${{ steps.extract.outputs.owner_repo }}" - echo "Requirement Issue: ${{ steps.extract.outputs.issue_num }}" - echo "Requirement URL: ${{ steps.extract.outputs.requirement_url }}" - echo "DONE." \ No newline at end of file + -f body="$NEW_BODY" \ No newline at end of file