Skip to content

Update DevFest details: Budapest (slug: budapest) #438

Update DevFest details: Budapest (slug: budapest)

Update DevFest details: Budapest (slug: budapest) #438

Workflow file for this run

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)}`
});
}