Update composer command path in bookstack.sh (#9656) #6113
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
| name: Create Changelog Pull Request | |
| on: | |
| push: | |
| branches: ["main"] | |
| workflow_dispatch: | |
| jobs: | |
| update-changelog-pull-request: | |
| if: github.repository == 'community-scripts/ProxmoxVE' | |
| runs-on: ubuntu-latest | |
| env: | |
| CONFIG_PATH: .github/changelog-pr-config.json | |
| BRANCH_NAME: github-action-update-changelog | |
| AUTOMATED_PR_LABEL: "automated pr" | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Generate a token | |
| id: generate-token | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ vars.APP_ID }} | |
| private-key: ${{ secrets.APP_PRIVATE_KEY }} | |
| - name: Generate a token for PR approval and merge | |
| id: generate-token-merge | |
| uses: actions/create-github-app-token@v1 | |
| with: | |
| app-id: ${{ secrets.APP_ID_APPROVE_AND_MERGE }} | |
| private-key: ${{ secrets.APP_KEY_APPROVE_AND_MERGE }} | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| - name: Get latest dates in changelog | |
| run: | | |
| DATES=$(grep -E '^## [0-9]{4}-[0-9]{2}-[0-9]{2}' CHANGELOG.md | head -n 2 | awk '{print $2}') | |
| LATEST_DATE=$(echo "$DATES" | sed -n '1p') | |
| SECOND_LATEST_DATE=$(echo "$DATES" | sed -n '2p') | |
| TODAY=$(date -u +%Y-%m-%d) | |
| echo "TODAY=$TODAY" >> $GITHUB_ENV | |
| if [[ "$LATEST_DATE" == "$TODAY" ]]; then | |
| echo "LATEST_DATE=$SECOND_LATEST_DATE" >> $GITHUB_ENV | |
| else | |
| echo "LATEST_DATE=$LATEST_DATE" >> $GITHUB_ENV | |
| fi | |
| - name: Get categorized pull requests | |
| id: get-categorized-prs | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| async function main() { | |
| const fs = require('fs').promises; | |
| const path = require('path'); | |
| const configPath = path.resolve(process.env.CONFIG_PATH); | |
| const fileContent = await fs.readFile(configPath, 'utf-8'); | |
| const changelogConfig = JSON.parse(fileContent); | |
| const categorizedPRs = changelogConfig.map(obj => ({ | |
| ...obj, | |
| notes: [], | |
| subCategories: obj.subCategories ?? ( | |
| obj.labels.includes("update script") ? [ | |
| { title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] }, | |
| { title: "✨ New Features", labels: ["feature"], notes: [] }, | |
| { title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] }, | |
| { title: "🔧 Refactor", labels: ["refactor"], notes: [] }, | |
| ] : | |
| obj.labels.includes("maintenance") ? [ | |
| { title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] }, | |
| { title: "✨ New Features", labels: ["feature"], notes: [] }, | |
| { title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] }, | |
| { title: "📡 API", labels: ["api"], notes: [] }, | |
| { title: "Github", labels: ["github"], notes: [] }, | |
| { title: "📝 Documentation", labels: ["maintenance"], notes: [] }, | |
| { title: "🔧 Refactor", labels: ["refactor"], notes: [] } | |
| ] : | |
| obj.labels.includes("website") ? [ | |
| { title: "🐞 Bug Fixes", labels: ["bugfix"], notes: [] }, | |
| { title: "✨ New Features", labels: ["feature"], notes: [] }, | |
| { title: "💥 Breaking Changes", labels: ["breaking change"], notes: [] }, | |
| { title: "Script Information", labels: ["json"], notes: [] } | |
| ] : [] | |
| ) | |
| })); | |
| const latestDateInChangelog = new Date(process.env.LATEST_DATE); | |
| latestDateInChangelog.setUTCHours(23, 59, 59, 999); | |
| const { data: pulls } = await github.rest.pulls.list({ | |
| owner: context.repo.owner, | |
| repo: "ProxmoxVE", | |
| base: "main", | |
| state: "closed", | |
| sort: "updated", | |
| direction: "desc", | |
| per_page: 100, | |
| }); | |
| const filteredPRs = pulls.filter(pr => | |
| pr.merged_at && | |
| new Date(pr.merged_at) > latestDateInChangelog && | |
| !pr.labels.some(label => | |
| ["invalid", "wontdo", process.env.AUTOMATED_PR_LABEL].includes(label.name.toLowerCase()) | |
| ) | |
| ); | |
| for (const pr of filteredPRs) { | |
| const prLabels = pr.labels.map(label => label.name.toLowerCase()); | |
| if (pr.user.login.includes("push-app-to-main[bot]")) { | |
| const scriptName = pr.title; | |
| try { | |
| const { data: relatedIssues } = await github.rest.issues.listForRepo({ | |
| owner: context.repo.owner, | |
| repo: "ProxmoxVED", | |
| state: "all", | |
| labels: ["Started Migration To ProxmoxVE"] | |
| }); | |
| const matchingIssue = relatedIssues.find(issue => | |
| issue.title.toLowerCase().includes(scriptName.toLowerCase()) | |
| ); | |
| if (matchingIssue) { | |
| const issueAuthor = matchingIssue.user.login; | |
| const issueAuthorUrl = `https://github.com/${issueAuthor}`; | |
| prNote = `- ${pr.title} [@${issueAuthor}](${issueAuthorUrl}) ([#${pr.number}](${pr.html_url}))`; | |
| } | |
| else { | |
| prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`; | |
| } | |
| } catch (error) { | |
| console.error(`Error fetching related issues: ${error}`); | |
| prNote = `- ${pr.title} ([#${pr.number}](${pr.html_url}))`; | |
| } | |
| }else{ | |
| prNote = `- ${pr.title} [@${pr.user.login}](https://github.com/${pr.user.login}) ([#${pr.number}](${pr.html_url}))`; | |
| } | |
| if (prLabels.includes("new script")) { | |
| const newScriptCategory = categorizedPRs.find(category => | |
| category.title === "New Scripts" || category.labels.includes("new script")); | |
| if (newScriptCategory) { | |
| newScriptCategory.notes.push(prNote); | |
| } | |
| } else { | |
| let categorized = false; | |
| const priorityCategories = categorizedPRs.slice(); | |
| // Priority order for content-type labels (highest priority first) | |
| const subCategoryPriority = ["breaking change", "bugfix", "feature", "refactor"]; | |
| for (const category of priorityCategories) { | |
| if (categorized) break; | |
| if (category.labels.some(label => prLabels.includes(label))) { | |
| if (category.subCategories && category.subCategories.length > 0) { | |
| // Find subcategory by priority order instead of first match | |
| let subCategory = null; | |
| for (const priorityLabel of subCategoryPriority) { | |
| if (prLabels.includes(priorityLabel)) { | |
| subCategory = category.subCategories.find(sub => | |
| sub.labels.includes(priorityLabel) | |
| ); | |
| if (subCategory) break; | |
| } | |
| } | |
| // Fallback: check for any other subcategory match (api, github, json, etc.) | |
| if (!subCategory) { | |
| subCategory = category.subCategories.find(sub => | |
| sub.labels.some(label => prLabels.includes(label)) | |
| ); | |
| } | |
| if (subCategory) { | |
| subCategory.notes.push(prNote); | |
| } else { | |
| category.notes.push(prNote); | |
| } | |
| } else { | |
| category.notes.push(prNote); | |
| } | |
| categorized = true; | |
| } | |
| } | |
| // Fallback: Add to Uncategorized if no category matched | |
| if (!categorized) { | |
| const uncategorized = categorizedPRs.find(category => | |
| category.title.includes("Uncategorized") || category.labels.includes("needs triage")); | |
| if (uncategorized) { | |
| uncategorized.notes.push(prNote); | |
| } | |
| } | |
| } | |
| } | |
| return categorizedPRs; | |
| } | |
| return await main(); | |
| - name: Update CHANGELOG.md | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const fs = require('fs').promises; | |
| const path = require('path'); | |
| const today = process.env.TODAY; | |
| const latestDateInChangelog = process.env.LATEST_DATE; | |
| const changelogPath = path.resolve('CHANGELOG.md'); | |
| const categorizedPRs = ${{ steps.get-categorized-prs.outputs.result }}; | |
| console.log(JSON.stringify(categorizedPRs, null, 2)); | |
| let newReleaseNotes = `## ${today}\n\n`; | |
| for (const { title, notes, subCategories } of categorizedPRs) { | |
| const hasSubcategories = subCategories && subCategories.length > 0; | |
| const hasMainNotes = notes.length > 0; | |
| const hasSubNotes = hasSubcategories && subCategories.some(sub => sub.notes && sub.notes.length > 0); | |
| if (hasMainNotes || hasSubNotes) { | |
| newReleaseNotes += `### ${title}\n\n`; | |
| } | |
| if (hasMainNotes) { | |
| newReleaseNotes += ` ${notes.join("\n")}\n\n`; | |
| } | |
| if (hasSubcategories) { | |
| for (const { title: subTitle, notes: subNotes } of subCategories) { | |
| if (subNotes && subNotes.length > 0) { | |
| newReleaseNotes += ` - #### ${subTitle}\n\n`; | |
| newReleaseNotes += ` ${subNotes.join("\n ")}\n\n`; | |
| } | |
| } | |
| } | |
| } | |
| const changelogContent = await fs.readFile(changelogPath, 'utf-8'); | |
| const changelogIncludesTodaysReleaseNotes = changelogContent.includes(`\n## ${today}`); | |
| const regex = changelogIncludesTodaysReleaseNotes | |
| ? new RegExp(`## ${today}.*(?=## ${latestDateInChangelog})`, "gs") | |
| : new RegExp(`(?=## ${latestDateInChangelog})`, "gs"); | |
| const newChangelogContent = changelogContent.replace(regex, newReleaseNotes); | |
| await fs.writeFile(changelogPath, newChangelogContent); | |
| - name: Check for changes | |
| id: verify-diff | |
| run: | | |
| git diff --quiet . || echo "changed=true" >> $GITHUB_ENV | |
| - name: Commit and push changes | |
| if: env.changed == 'true' | |
| run: | | |
| git config --global user.name "github-actions[bot]" | |
| git config --global user.email "github-actions[bot]@users.noreply.github.com" | |
| git add CHANGELOG.md | |
| git commit -m "Update CHANGELOG.md" | |
| git checkout -b $BRANCH_NAME || git checkout $BRANCH_NAME | |
| git push origin $BRANCH_NAME --force | |
| - name: Create pull request if not exists | |
| if: env.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token.outputs.token }} | |
| run: | | |
| PR_EXISTS=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') | |
| if [ -z "$PR_EXISTS" ]; then | |
| gh pr create --title "[Github Action] Update CHANGELOG.md" \ | |
| --body "This PR is auto-generated by a Github Action to update the CHANGELOG.md file." \ | |
| --head $BRANCH_NAME \ | |
| --base main \ | |
| --label "$AUTOMATED_PR_LABEL" | |
| fi | |
| - name: Approve pull request | |
| if: env.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') | |
| if [ -n "$PR_NUMBER" ]; then | |
| gh pr review $PR_NUMBER --approve | |
| fi | |
| - name: Approve pull request and merge | |
| if: env.changed == 'true' | |
| env: | |
| GH_TOKEN: ${{ steps.generate-token-merge.outputs.token }} | |
| run: | | |
| git config --global user.name "github-actions-automege[bot]" | |
| git config --global user.email "github-actions-automege[bot]@users.noreply.github.com" | |
| PR_NUMBER=$(gh pr list --head "${BRANCH_NAME}" --json number --jq '.[].number') | |
| if [ -n "$PR_NUMBER" ]; then | |
| gh pr review $PR_NUMBER --approve | |
| gh pr merge $PR_NUMBER --squash --admin | |
| fi |