Add workflow_dispatch inputs for studio docs update#24827
Conversation
There was a problem hiding this comment.
Pull request overview
This PR enhances the ABP Studio documentation update workflow by adding manual triggering capability via workflow_dispatch inputs. This allows the workflow to be triggered both automatically via repository_dispatch events and manually through the GitHub UI, providing more flexibility for documentation updates.
Changes:
- Added
workflow_dispatchtrigger with inputs for version, name, notes, URL, and target_branch - Unified payload extraction to handle both
repository_dispatchandworkflow_dispatchevent sources - Enhanced the workflow with better error handling, idempotency, and comprehensive logging
| except InvalidVersion: | ||
| print(f"❌ Invalid Studio version: {studio_ver}") | ||
| exit(1) |
There was a problem hiding this comment.
Python script uses exit(1) which may not propagate failure to the shell. In Python embedded in shell scripts, using exit(1) calls the built-in exit function which may not set the shell's exit code properly. Use import sys; sys.exit(1) instead to ensure the failure is properly propagated to the shell and causes the GitHub Actions step to fail as intended.
| run: | | ||
| required_keys=(version name notes url target_branch) | ||
| for key in "${required_keys[@]}"; do | ||
| value="$(jq -r --arg k "$key" '.client_payload[$k] // ""' "$GITHUB_EVENT_PATH")" | ||
| if [ -z "$value" ] || [ "$value" = "null" ]; then | ||
| echo "Missing payload field: $key" | ||
| exit 1 | ||
| fi | ||
| done | ||
| if [ -z "$VERSION" ] || [ "$VERSION" = "null" ]; then | ||
| echo "❌ Missing: version" | ||
| exit 1 | ||
| fi | ||
| if [ -z "$NAME" ] || [ "$NAME" = "null" ]; then | ||
| echo "❌ Missing: name" | ||
| exit 1 | ||
| fi | ||
| if [ -z "$URL" ] || [ "$URL" = "null" ]; then | ||
| echo "❌ Missing: url" | ||
| exit 1 | ||
| fi | ||
| if [ ! -s .tmp/raw-notes.txt ]; then | ||
| echo "❌ Missing: release notes" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "✅ Payload validated" |
There was a problem hiding this comment.
Missing validation for workflow_dispatch inputs. While repository_dispatch payloads are validated (lines 64-82), the workflow_dispatch inputs could contain malicious or malformed data that bypasses validation if an attacker has workflow dispatch permissions. The current validation only checks for empty values but doesn't validate format (e.g., version should match semver pattern, URL should be a valid GitHub URL). Add input validation for version format, URL scheme, and sanitize other inputs before use.
| echo "EXISTING_FORMAT<<DELIMITER_EOF" | ||
| head -50 "$FILE" | sed 's/DELIMITER_EOF/DELIMITER_E0F/g' | ||
| echo "DELIMITER_EOF" | ||
| } >> $GITHUB_OUTPUT | ||
| else | ||
| { | ||
| echo "EXISTING_FORMAT<<DELIMITER_EOF" | ||
| echo "# ABP Studio Release Notes" | ||
| echo "" | ||
| echo "## 2.1.0 (2025-12-08) Latest" | ||
| echo "- Enhanced Module Installation UI" | ||
| echo "- Added AI Management option" | ||
| echo "DELIMITER_EOF" |
There was a problem hiding this comment.
The sed substitution on line 127 replaces DELIMITER_EOF with DELIMITER_E0F (zero instead of O), but this replacement is not reversed when the content is used. This could potentially corrupt the output if the original content contains the string "DELIMITER_EOF". Consider using a more unique delimiter that is unlikely to appear in the actual content, or ensure the replacement is reversed when needed.
| echo "EXISTING_FORMAT<<DELIMITER_EOF" | |
| head -50 "$FILE" | sed 's/DELIMITER_EOF/DELIMITER_E0F/g' | |
| echo "DELIMITER_EOF" | |
| } >> $GITHUB_OUTPUT | |
| else | |
| { | |
| echo "EXISTING_FORMAT<<DELIMITER_EOF" | |
| echo "# ABP Studio Release Notes" | |
| echo "" | |
| echo "## 2.1.0 (2025-12-08) Latest" | |
| echo "- Enhanced Module Installation UI" | |
| echo "- Added AI Management option" | |
| echo "DELIMITER_EOF" | |
| echo "EXISTING_FORMAT<<ABP_STUDIO_RELEASE_NOTES_EOF" | |
| head -50 "$FILE" | |
| echo "ABP_STUDIO_RELEASE_NOTES_EOF" | |
| } >> $GITHUB_OUTPUT | |
| else | |
| { | |
| echo "EXISTING_FORMAT<<ABP_STUDIO_RELEASE_NOTES_EOF" | |
| echo "# ABP Studio Release Notes" | |
| echo "" | |
| echo "## 2.1.0 (2025-12-08) Latest" | |
| echo "- Enhanced Module Installation UI" | |
| echo "- Added AI Management option" | |
| echo "ABP_STUDIO_RELEASE_NOTES_EOF" |
| -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ | ||
| "https://api.github.com/repos/abpframework/abp/releases?per_page=20") | ||
|
|
||
| # Filter stable releases (exclude preview, rc, beta, dev) | ||
| ABP_VERSION=$(echo "$RELEASES" | jq -r ' | ||
| [.[] | select( | ||
| (.prerelease == false) and | ||
| (.tag_name | test("preview|rc|beta|dev"; "i") | not) | ||
| )] | first | .tag_name | ||
| ') | ||
|
|
||
| if [ -z "$ABP_VERSION" ] || [ "$ABP_VERSION" = "null" ]; then | ||
| echo "❌ Could not determine latest ABP version" | ||
| echo "❌ Could not determine latest stable ABP version" | ||
| exit 1 | ||
| fi | ||
|
|
||
| echo "Latest ABP version: $ABP_VERSION" | ||
| echo "✅ Latest stable ABP version: $ABP_VERSION" | ||
| echo "ABP_VERSION=$ABP_VERSION" >> $GITHUB_ENV | ||
|
|
||
| # ------------------------------------------------- | ||
| # Update version-mapping.md (INSIDE table) | ||
| # Update version-mapping.md (smart range expansion) | ||
| # ------------------------------------------------- | ||
| - name: Update version-mapping.md (smart) | ||
| - name: Update version-mapping.md | ||
| env: | ||
| STUDIO_VERSION: ${{ github.event.client_payload.version }} | ||
| ABP_VERSION: ${{ env.ABP_VERSION }} | ||
| STUDIO_VERSION: ${{ steps.payload.outputs.version }} | ||
| run: | | ||
| FILE="docs/en/studio/version-mapping.md" | ||
|
|
||
| ABP_VERSION="${{ env.ABP_VERSION }}" | ||
|
|
||
| mkdir -p docs/en/studio | ||
|
|
||
|
|
||
| # Create file if doesn't exist | ||
| if [ ! -f "$FILE" ]; then | ||
| echo "| ABP Studio Version | ABP Version |" > "$FILE" | ||
| echo "|-------------------|-------------|" >> "$FILE" | ||
| echo "| $STUDIO_VERSION | $ABP_VERSION |" >> "$FILE" | ||
| cat > "$FILE" <<EOF | ||
| # ABP Studio and ABP Startup Template Version Mappings | ||
|
|
||
| | **ABP Studio Version** | **ABP Version of Startup Template** | | ||
| |------------------------|-------------------------------------| | ||
| | $STUDIO_VERSION | $ABP_VERSION | | ||
| EOF | ||
| echo "MAPPING_UPDATED=true" >> $GITHUB_ENV | ||
| exit 0 | ||
| fi | ||
|
|
||
| python3 <<EOF | ||
|
|
||
| # Use Python for smart version range handling | ||
| python3 <<'PYTHON_EOF' | ||
| import os | ||
| import re | ||
| from packaging.version import Version | ||
| from packaging.version import Version, InvalidVersion | ||
|
|
||
| studio = Version(os.environ["STUDIO_VERSION"]) | ||
| abp = os.environ["ABP_VERSION"] | ||
| studio_ver = os.environ["STUDIO_VERSION"] | ||
| abp_ver = os.environ["ABP_VERSION"] | ||
| file_path = "docs/en/studio/version-mapping.md" | ||
|
|
||
| with open(file_path) as f: | ||
| try: | ||
| studio = Version(studio_ver) | ||
| except InvalidVersion: | ||
| print(f"❌ Invalid Studio version: {studio_ver}") | ||
| exit(1) | ||
|
|
||
| with open(file_path, 'r') as f: | ||
| lines = f.readlines() | ||
|
|
||
| header = lines[:2] | ||
| rows = lines[2:] | ||
| # Find table start (skip SEO and headers) | ||
| table_start = 0 | ||
| for i, line in enumerate(lines): | ||
| if line.strip().startswith('|') and '**ABP Studio Version**' in line: | ||
| table_start = i | ||
| break | ||
|
|
||
| if table_start == 0: | ||
| print("❌ Could not find version mapping table") | ||
| exit(1) | ||
|
|
||
| # Extract header + separator + data rows | ||
| header_lines = lines[:table_start+2] # Keep everything before data rows | ||
| data_rows = [l for l in lines[table_start+2:] if l.strip().startswith('|')] | ||
|
|
||
| new_rows = [] | ||
| handled = False | ||
|
|
||
| def parse_range(r): | ||
| if "-" in r: | ||
| a, b = r.split("-") | ||
| return Version(a.strip()), Version(b.strip()) | ||
| v = Version(r.strip()) | ||
| return v, v | ||
|
|
||
| for row in rows: | ||
| m = re.match(r"\|\s*(.+?)\s*\|\s*(.+?)\s*\|", row) | ||
| if not m: | ||
| def parse_version_range(version_str): | ||
| """Parse '2.1.5 - 2.1.9' or '2.1.5' into (start, end)""" | ||
| version_str = version_str.strip() | ||
|
|
||
| if '–' in version_str or '-' in version_str: | ||
| # Handle both em-dash and hyphen | ||
| parts = re.split(r'\s*[–-]\s*', version_str) | ||
| if len(parts) == 2: | ||
| try: | ||
| return Version(parts[0].strip()), Version(parts[1].strip()) | ||
| except InvalidVersion: | ||
| return None, None | ||
|
|
||
| try: | ||
| v = Version(version_str) | ||
| return v, v | ||
| except InvalidVersion: | ||
| return None, None | ||
|
|
||
| def format_row(studio_range, abp_version): | ||
| """Format a table row with proper spacing""" | ||
| return f"| {studio_range:<22} | {abp_version:<27} |\n" | ||
|
|
||
| # Process existing rows | ||
| for row in data_rows: | ||
| match = re.match(r'\|\s*(.+?)\s*\|\s*(.+?)\s*\|', row) | ||
| if not match: | ||
| continue | ||
|
|
||
| existing_studio_range = match.group(1).strip() | ||
| existing_abp = match.group(2).strip() | ||
|
|
||
| # Only consider rows with matching ABP version | ||
| if existing_abp != abp_ver: | ||
| new_rows.append(row) | ||
| continue | ||
|
|
||
| studio_range, abp_version = m.groups() | ||
|
|
||
| if abp_version != abp: | ||
| start_ver, end_ver = parse_version_range(existing_studio_range) | ||
| if start_ver is None or end_ver is None: | ||
| new_rows.append(row) | ||
| continue | ||
|
|
||
| start, end = parse_range(studio_range) | ||
|
|
||
| if start <= studio <= end: | ||
| # Check if current studio version is in this range | ||
| if start_ver <= studio <= end_ver: | ||
| print(f"✅ Studio version {studio_ver} already covered in range {existing_studio_range}") | ||
| handled = True | ||
| new_rows.append(row) | ||
| elif studio == end.next_patch(): | ||
| handled = True | ||
| new_rows.append(f"| {start} - {studio} | {abp} |\\n") | ||
|
|
||
| # Check if we should extend the range | ||
| elif end_ver < studio: | ||
| # Calculate if studio is the next logical version | ||
| # For patch versions: 2.1.9 -> 2.1.10 | ||
| # For minor versions: 2.1.9 -> 2.2.0 | ||
|
|
||
| # Simple heuristic: if major.minor match and patch increments, extend range | ||
| if (start_ver.major == studio.major and | ||
| start_ver.minor == studio.minor and | ||
| studio.micro <= end_ver.micro + 5): # Allow small gaps | ||
|
|
||
| new_range = f"{start_ver} - {studio}" | ||
| new_rows.append(format_row(new_range, abp_ver)) | ||
| print(f"✅ Extended range: {new_range}") | ||
| handled = True | ||
| else: | ||
| new_rows.append(row) | ||
| else: | ||
| new_rows.append(row) | ||
|
|
||
| # If not handled, add new row at top of data | ||
| if not handled: | ||
| new_rows.insert(0, f"| {studio} | {abp} |\\n") | ||
|
|
||
| with open(file_path, "w") as f: | ||
| f.writelines(header + new_rows) | ||
| EOF | ||
| new_row = format_row(str(studio), abp_ver) | ||
| new_rows.insert(0, new_row) | ||
| print(f"✅ Added new mapping: {studio_ver} -> {abp_ver}") | ||
|
|
||
| # Write updated file | ||
| with open(file_path, 'w') as f: | ||
| f.writelines(header_lines + new_rows) | ||
|
|
||
| print("MAPPING_UPDATED=true") | ||
| PYTHON_EOF | ||
|
|
||
| echo "MAPPING_UPDATED=true" >> $GITHUB_ENV | ||
|
|
||
| echo "=== Updated version-mapping.md preview ===" | ||
| head -35 "$FILE" | ||
| echo "==========================================" | ||
|
|
||
| # ------------------------------------------------- | ||
| # Check for changes | ||
| # ------------------------------------------------- | ||
| - name: Check for changes | ||
| id: changes | ||
| run: | | ||
| git add docs/en/studio | ||
| git add docs/en/studio/ | ||
|
|
||
| if git diff --cached --quiet; then | ||
| echo "has_changes=false" >> $GITHUB_OUTPUT | ||
| echo "⚠️ No changes detected" | ||
| else | ||
| echo "has_changes=true" >> $GITHUB_OUTPUT | ||
| echo "✅ Changes detected:" | ||
| git diff --cached --stat | ||
| fi | ||
|
|
||
| # ------------------------------------------------- | ||
| # Commit & push | ||
| # ------------------------------------------------- | ||
| - name: Commit & push | ||
| - name: Commit and push | ||
| if: steps.changes.outputs.has_changes == 'true' | ||
| env: | ||
| VERSION: ${{ steps.payload.outputs.version }} | ||
| NAME: ${{ steps.payload.outputs.name }} | ||
| run: | | ||
| git commit -m "docs(studio): release ${{ github.event.client_payload.version }}" | ||
| git push -u origin "$BRANCH" | ||
| git commit -m "docs(studio): update documentation for release $VERSION | ||
|
|
||
| - Updated release notes for $VERSION | ||
| - Updated version mapping with ABP ${{ env.ABP_VERSION }} | ||
|
|
||
| Release: $NAME" | ||
|
|
||
| git push -f origin "$BRANCH" | ||
|
|
||
| # ------------------------------------------------- | ||
| # Create PR | ||
| # Create or update PR | ||
| # ------------------------------------------------- | ||
| - name: Create PR | ||
| - name: Create or update PR | ||
| if: steps.changes.outputs.has_changes == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.BOT_SECRET }} | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| VERSION: ${{ steps.payload.outputs.version }} | ||
| NAME: ${{ steps.payload.outputs.name }} | ||
| URL: ${{ steps.payload.outputs.url }} | ||
| TARGET_BRANCH: ${{ steps.payload.outputs.target_branch }} | ||
| run: | | ||
| PR_URL=$(gh pr create \ | ||
| --title "docs(studio): release ${{ github.event.client_payload.version }}" \ | ||
| --body "Automated documentation update for ABP Studio." \ | ||
| --base "${{ github.event.client_payload.target_branch }}" \ | ||
| --head "$BRANCH") | ||
|
|
||
| echo "PR_URL=$PR_URL" >> $GITHUB_ENV | ||
| # Check for existing PR | ||
| EXISTING_PR=$(gh pr list \ | ||
| --head "$BRANCH" \ | ||
| --base "$TARGET_BRANCH" \ | ||
| --json number \ | ||
| --jq '.[0].number' 2>/dev/null || echo "") | ||
|
|
||
| PR_BODY="Automated documentation update for ABP Studio release **$VERSION**. | ||
|
|
||
| ## Release Information | ||
| - **Version**: $VERSION | ||
| - **Name**: $NAME | ||
| - **Release**: [View on GitHub]($URL) | ||
| - **ABP Framework Version**: ${{ env.ABP_VERSION }} | ||
|
|
||
| ## Changes | ||
| - ✅ Updated [release-notes.md](docs/en/studio/release-notes.md) | ||
| - ✅ Updated [version-mapping.md](docs/en/studio/version-mapping.md) | ||
|
|
||
| --- | ||
|
|
||
| *This PR was automatically generated by the [update-studio-docs workflow](.github/workflows/update-studio-docs.yml)*" | ||
|
|
||
| if [ -n "$EXISTING_PR" ]; then | ||
| echo "🔄 Updating existing PR #$EXISTING_PR" | ||
|
|
||
| gh pr edit "$EXISTING_PR" \ | ||
| --title "docs(studio): release $VERSION - $NAME" \ | ||
| --body "$PR_BODY" | ||
|
|
||
| echo "PR_NUMBER=$EXISTING_PR" >> $GITHUB_ENV | ||
| else | ||
| echo "📝 Creating new PR" | ||
|
|
||
| sleep 2 # Wait for GitHub to sync | ||
|
|
||
| PR_URL=$(gh pr create \ | ||
| --title "docs(studio): release $VERSION - $NAME" \ | ||
| --body "$PR_BODY" \ | ||
| --base "$TARGET_BRANCH" \ | ||
| --head "$BRANCH") | ||
|
|
||
| PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') | ||
| echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV | ||
| echo "✅ Created PR #$PR_NUMBER: $PR_URL" | ||
| fi | ||
|
|
||
| # ------------------------------------------------- | ||
| # Enable auto-merge (branch protection safe) | ||
| # Enable auto-merge (safe with branch protection) | ||
| # ------------------------------------------------- | ||
| - name: Enable auto-merge | ||
| if: steps.changes.outputs.has_changes == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.BOT_SECRET }} | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
There was a problem hiding this comment.
Secret token usage changed from BOT_SECRET to GITHUB_TOKEN. The workflow now uses the default GITHUB_TOKEN instead of a custom BOT_SECRET for GitHub CLI operations (lines 296, 506, 565). This changes the author/actor for API calls and may affect permissions, particularly for creating PRs and enabling auto-merge. The default GITHUB_TOKEN may not have sufficient permissions for auto-merge depending on repository settings. Verify that GITHUB_TOKEN has the necessary permissions, or document that BOT_SECRET may still be needed for certain operations.
| - name: Format release notes with AI | ||
| id: ai | ||
| continue-on-error: true | ||
| uses: actions/ai-inference@v1 |
There was a problem hiding this comment.
The action actions/ai-inference@v1 does not exist in the GitHub Actions marketplace. This is likely a placeholder or future action. Since the step has continue-on-error: true, the workflow will continue if this fails, but this should be verified or replaced with an actual AI service integration (such as OpenAI API calls or GitHub Copilot API) if AI formatting is desired. Otherwise, document that this is intentionally a placeholder for future functionality.
| PR_URL=$(gh pr create \ | ||
| --title "docs(studio): release $VERSION - $NAME" \ | ||
| --body "$PR_BODY" \ | ||
| --base "$TARGET_BRANCH" \ | ||
| --head "$BRANCH") | ||
|
|
||
| PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') |
There was a problem hiding this comment.
Fragile PR number extraction using grep. Line 554 extracts the PR number from the URL using grep -oE '[0-9]+$', which assumes the PR URL ends with the number. While this is typically true for GitHub PR URLs, a more robust approach would be to use the --json flag with gh pr create to get structured output, or use gh pr view with JSON output to reliably extract the PR number. This would prevent potential failures if GitHub changes its URL format.
| PR_URL=$(gh pr create \ | |
| --title "docs(studio): release $VERSION - $NAME" \ | |
| --body "$PR_BODY" \ | |
| --base "$TARGET_BRANCH" \ | |
| --head "$BRANCH") | |
| PR_NUMBER=$(echo "$PR_URL" | grep -oE '[0-9]+$') | |
| PR_JSON=$(gh pr create \ | |
| --title "docs(studio): release $VERSION - $NAME" \ | |
| --body "$PR_BODY" \ | |
| --base "$TARGET_BRANCH" \ | |
| --head "$BRANCH" \ | |
| --json number,url) | |
| PR_NUMBER=$(echo "$PR_JSON" | jq -r '.number') | |
| PR_URL=$(echo "$PR_JSON" | jq -r '.url') |
| Version: ${{ steps.payload.outputs.version }} | ||
| Name: ${{ steps.payload.outputs.name }} | ||
| Raw notes: | ||
| $(cat .tmp/raw-notes.txt) |
There was a problem hiding this comment.
Potential code injection vulnerability in AI prompt. The raw notes file content is directly embedded into the AI prompt using command substitution $(cat .tmp/raw-notes.txt) without proper escaping. If the notes contain special characters or command sequences, this could lead to unexpected behavior. Consider using proper quoting mechanisms or passing the content more safely.
| $(cat .tmp/raw-notes.txt) | |
| ${{ steps.payload.outputs.notes }} |
| python3 <<EOF | ||
|
|
||
| # Use Python for smart version range handling | ||
| python3 <<'PYTHON_EOF' |
There was a problem hiding this comment.
Potential issue with delimiter collision. The script uses PYTHON_EOF as a heredoc delimiter (line 341), but if the embedded Python code contains the string "PYTHON_EOF", it could terminate the heredoc prematurely. While this is unlikely, consider using a more unique delimiter like PYTHON_SCRIPT_EOF_MARKER_12345 to avoid any potential conflicts.
|
|
||
| if [ "${{ steps.changes.outputs.has_changes }}" = "true" ]; then | ||
| echo "### ✅ Changes Applied" >> $GITHUB_STEP_SUMMARY | ||
| echo "- Release notes updated: ${{ env.VERSION_UPDATED }}" >> $GITHUB_STEP_SUMMARY |
There was a problem hiding this comment.
Variable used before conditional check. The workflow sets VERSION_UPDATED=false on line 229 when a version already exists, but then uses ${{ env.VERSION_UPDATED }} in the summary step (line 595). However, if the version doesn't already exist, VERSION_UPDATED is set to true on line 281. This works, but there's a potential issue: if the step on line 215 fails before reaching line 229 or 281, the variable may be undefined. Consider initializing VERSION_UPDATED to a default value early in the workflow, or ensure the summary step handles undefined values gracefully.
| sleep 2 # Wait for GitHub to sync | ||
|
|
||
| PR_URL=$(gh pr create \ | ||
| --title "docs(studio): release $VERSION - $NAME" \ | ||
| --body "$PR_BODY" \ | ||
| --base "$TARGET_BRANCH" \ | ||
| --head "$BRANCH") |
There was a problem hiding this comment.
Hardcoded 2-second sleep before PR creation. The workflow includes sleep 2 (line 546) with a comment "Wait for GitHub to sync". This arbitrary delay is a code smell and may not be sufficient if GitHub's API is experiencing delays. Consider using a retry mechanism with exponential backoff, or rely on the GitHub CLI's built-in retry logic instead of a fixed sleep. If the sleep is necessary, document why 2 seconds was chosen and under what conditions it might need to be adjusted.
| sleep 2 # Wait for GitHub to sync | |
| PR_URL=$(gh pr create \ | |
| --title "docs(studio): release $VERSION - $NAME" \ | |
| --body "$PR_BODY" \ | |
| --base "$TARGET_BRANCH" \ | |
| --head "$BRANCH") | |
| # Retry PR creation with exponential backoff to handle transient GitHub sync/API delays | |
| MAX_RETRIES=3 | |
| DELAY=2 | |
| ATTEMPT=1 | |
| while [ $ATTEMPT -le $MAX_RETRIES ]; do | |
| echo "Attempt $ATTEMPT/$MAX_RETRIES: creating PR..." | |
| # Temporarily disable 'exit on error' to handle retries explicitly | |
| set +e | |
| PR_URL=$(gh pr create \ | |
| --title "docs(studio): release $VERSION - $NAME" \ | |
| --body "$PR_BODY" \ | |
| --base "$TARGET_BRANCH" \ | |
| --head "$BRANCH") | |
| STATUS=$? | |
| set -e | |
| if [ $STATUS -eq 0 ] && [ -n "$PR_URL" ]; then | |
| break | |
| fi | |
| if [ $ATTEMPT -lt $MAX_RETRIES ]; then | |
| echo "gh pr create failed (status: $STATUS). Retrying in $DELAY seconds..." | |
| sleep $DELAY | |
| DELAY=$((DELAY * 2)) | |
| ATTEMPT=$((ATTEMPT + 1)) | |
| else | |
| echo "gh pr create failed after $MAX_RETRIES attempts (last status: $STATUS)." | |
| exit $STATUS | |
| fi | |
| done |
No description provided.