Update DevFest details: Budapest (slug: budapest) #438
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: Process URL Update Requests | |
| on: | |
| issues: | |
| types: [labeled] | |
| # Add permissions block to grant access to issues and contents | |
| permissions: | |
| issues: write | |
| contents: write | |
| pull-requests: write | |
| jobs: | |
| process-url-request: | |
| if: | | |
| contains(github.event.issue.labels.*.name, 'update-url') && | |
| !contains(github.event.issue.labels.*.name, 'processed') | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: Production | |
| url: ${{ github.server_url }}/${{ github.repository }}/issues/${{ github.event.issue.number }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| token: ${{ secrets.PAT_GITHUB_TOKEN }} | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - name: Install dependencies | |
| run: npm ci | |
| - name: Extract issue body | |
| id: extract | |
| uses: actions/github-script@v7 | |
| with: | |
| github-token: ${{ secrets.PAT_GITHUB_TOKEN }} | |
| script: | | |
| const issueBody = context.payload.issue.body; | |
| const issueTitle = context.payload.issue.title; | |
| // Extract slug from title | |
| const slugMatch = issueTitle.match(/\(slug:\s*([^)]+)\)/); | |
| const slug = slugMatch ? slugMatch[1].trim() : ''; | |
| // Extract city from title for default DevFest name | |
| const cityMatch = issueTitle.match(/Update DevFest details:\s*([^(]+)/); | |
| const city = cityMatch ? cityMatch[1].trim() : slug; | |
| const extractField = (id) => { | |
| const regex = new RegExp(`### ${id}[^#]*\\n\\n([^#]+)(?=###|$)`, 'ms'); | |
| const match = issueBody.match(regex); | |
| return match ? match[1].trim() : ''; | |
| }; | |
| const destinationUrl = extractField('Destination URL'); | |
| let devfestName = extractField('DevFest Name'); | |
| const devfestDate = extractField('DevFest Date'); | |
| // If DevFest name is empty, generate default format | |
| if (!devfestName) { | |
| const year = devfestDate ? new Date(devfestDate).getFullYear() : new Date().getFullYear(); | |
| devfestName = `DevFest ${city} ${year}`; | |
| } | |
| const data = { | |
| slug, | |
| destinationUrl, | |
| devfestDate, | |
| devfestName, | |
| updatedBy: context.payload.issue.user.login, | |
| updatedAt: new Date().toISOString() | |
| }; | |
| core.setOutput('data', JSON.stringify(data)); | |
| - name: Update JSON file | |
| uses: actions/github-script@v7 | |
| env: | |
| URL_DATA: ${{ steps.extract.outputs.data }} | |
| with: | |
| github-token: ${{ secrets.PAT_GITHUB_TOKEN }} | |
| script: | | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const { execSync } = require('child_process'); | |
| let inputData = JSON.parse(process.env.URL_DATA); | |
| const { slug, ...dataWithoutSlug } = inputData; | |
| // Validate required fields and formats | |
| const errors = []; | |
| if (!slug) { | |
| errors.push("Missing required field: slug in the issue title"); | |
| } | |
| if (!dataWithoutSlug.destinationUrl) { | |
| errors.push("Missing required field: destinationUrl"); | |
| } else if (!dataWithoutSlug.destinationUrl.startsWith('https://')) { | |
| errors.push("destinationUrl must be a valid HTTPS URL"); | |
| } | |
| if (!dataWithoutSlug.devfestDate) { | |
| errors.push("Missing required field: devfestDate"); | |
| } else { | |
| const dateRegex = /^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$/; | |
| if (!dateRegex.test(dataWithoutSlug.devfestDate)) { | |
| errors.push("devfestDate must be in YYYY-MM-DD format"); | |
| } | |
| } | |
| if (errors.length > 0) { | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `❌ Error: Invalid or missing fields:\n\n${errors.map(e => `- ${e}`).join('\n')}\n\nPlease update the issue with the correct information.` | |
| }); | |
| return; | |
| } | |
| try { | |
| // Update JSON file | |
| const dataFilePath = path.join(process.cwd(), 'data', 'devfest-data.json'); | |
| const currentData = JSON.parse(fs.readFileSync(dataFilePath, 'utf8')); | |
| // Check if slug exists | |
| const existingIndex = currentData.findIndex(entry => entry.slug === slug); | |
| if (existingIndex === -1) { | |
| throw new Error(`No existing entry found for slug: ${slug}. This workflow only supports updating existing entries.`); | |
| } | |
| // Get existing data and preserve metadata | |
| const existingData = currentData[existingIndex]; | |
| // Create updated entry by preserving existing metadata and updating only specific fields | |
| const updatedEntry = { | |
| ...existingData, | |
| destinationUrl: dataWithoutSlug.destinationUrl, | |
| devfestDate: dataWithoutSlug.devfestDate, | |
| devfestName: dataWithoutSlug.devfestName, | |
| updatedBy: dataWithoutSlug.updatedBy, | |
| updatedAt: dataWithoutSlug.updatedAt | |
| }; | |
| // Update the JSON file | |
| currentData[existingIndex] = updatedEntry; | |
| fs.writeFileSync(dataFilePath, JSON.stringify(currentData, null, 2) + '\n'); | |
| // Git operations | |
| execSync('git config user.name "github-actions[bot]"'); | |
| execSync('git config user.email "github-actions[bot]@users.noreply.github.com"'); | |
| const branchName = `update-devfest-${slug}-${Date.now()}`; | |
| execSync(`git checkout -b ${branchName}`); | |
| execSync(`git add ${dataFilePath}`); | |
| execSync(`git commit -m "Update DevFest data for ${slug}"`); | |
| execSync(`git push origin ${branchName}`); | |
| // Create PR | |
| const prResponse = await github.rest.pulls.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| title: `Update DevFest data for ${slug}`, | |
| body: [ | |
| `This PR updates the DevFest data for \`${slug}\` based on issue #${context.issue.number}.`, | |
| '', | |
| '**Changes:**', | |
| '```json', | |
| JSON.stringify(updatedEntry, null, 2), | |
| '```', | |
| '', | |
| '_Note: This branch will be automatically deleted after merging._' | |
| ].join('\n'), | |
| head: branchName, | |
| base: 'main' | |
| }); | |
| // Enable auto-delete for the PR's branch | |
| await github.rest.repos.update({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| delete_branch_on_merge: true | |
| }); | |
| // Add processed label | |
| await github.rest.issues.addLabels({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| labels: ['processed'] | |
| }); | |
| // Format success message | |
| const successMessage = [ | |
| `✅ URL update processed successfully!`, | |
| ` `, | |
| ` `, | |
| `**URL Details:**`, | |
| '```json', | |
| JSON.stringify({ | |
| slug, | |
| ...updatedEntry | |
| }, null, 2), | |
| '```', | |
| ` `, | |
| ` `, | |
| `The URL has been updated successfully while preserving existing metadata.`, | |
| ``, | |
| `A pull request has been created to update the JSON data file: ${prResponse.data.html_url}`, | |
| ``, | |
| `Note: If you need to make any changes in the future, feel free to create a new update request.` | |
| ].filter(Boolean).join('\n'); | |
| // Comment on the issue | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: successMessage | |
| }); | |
| // Close the issue | |
| await github.rest.issues.update({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| state: 'closed' | |
| }); | |
| } catch (error) { | |
| console.error('Error updating data:', error); | |
| await github.rest.issues.createComment({ | |
| issue_number: context.issue.number, | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| body: `❌ Error processing update: ${error.message}\n\nData received: ${JSON.stringify(dataWithoutSlug, null, 2)}` | |
| }); | |
| } |