Skip to content

Add workflow_dispatch inputs for studio docs update#24827

Merged
m-aliozkaya merged 1 commit intodevfrom
skoc10-patch-1
Feb 6, 2026
Merged

Add workflow_dispatch inputs for studio docs update#24827
m-aliozkaya merged 1 commit intodevfrom
skoc10-patch-1

Conversation

@skoc10
Copy link
Contributor

@skoc10 skoc10 commented Feb 6, 2026

No description provided.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_dispatch trigger with inputs for version, name, notes, URL, and target_branch
  • Unified payload extraction to handle both repository_dispatch and workflow_dispatch event sources
  • Enhanced the workflow with better error handling, idempotency, and comprehensive logging

Comment on lines +352 to +354
except InvalidVersion:
print(f"❌ Invalid Studio version: {studio_ver}")
exit(1)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines 64 to +82
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"
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +138
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"
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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"

Copilot uses AI. Check for mistakes.
Comment on lines +296 to +565
-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 }}
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
- name: Format release notes with AI
id: ai
continue-on-error: true
uses: actions/ai-inference@v1
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +548 to +554
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]+$')
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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')

Copilot uses AI. Check for mistakes.
Version: ${{ steps.payload.outputs.version }}
Name: ${{ steps.payload.outputs.name }}
Raw notes:
$(cat .tmp/raw-notes.txt)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
$(cat .tmp/raw-notes.txt)
${{ steps.payload.outputs.notes }}

Copilot uses AI. Check for mistakes.
python3 <<EOF

# Use Python for smart version range handling
python3 <<'PYTHON_EOF'
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.

if [ "${{ steps.changes.outputs.has_changes }}" = "true" ]; then
echo "### ✅ Changes Applied" >> $GITHUB_STEP_SUMMARY
echo "- Release notes updated: ${{ env.VERSION_UPDATED }}" >> $GITHUB_STEP_SUMMARY
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +546 to +552
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")
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
@m-aliozkaya m-aliozkaya merged commit 75c5f16 into dev Feb 6, 2026
8 checks passed
@m-aliozkaya m-aliozkaya deleted the skoc10-patch-1 branch February 6, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants