Skip to content

[CONTRIBUTION] Update fastsurfer container #190

[CONTRIBUTION] Update fastsurfer container

[CONTRIBUTION] Update fastsurfer container #190

name: Process Contribution Issues
permissions:
contents: read
pull-requests: write
on:
issues:
types: [opened]
jobs:
process-contribution:
if: contains(github.event.issue.title, '[CONTRIBUTION]')
runs-on: ubuntu-latest
permissions:
issues: write
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm install js-yaml pako
- name: Parse base64 encoded deflate compressed YAML from issue body
id: parse-yaml
uses: actions/github-script@v7
with:
script: |
const yaml = require('js-yaml');
const pako = require('pako');
const fs = require('fs');
const path = require('path');
const issueBody = context.payload.issue.body;
// Extract base64 encoded content from issue body
const base64Regex = /```base64\n([\s\S]*?)\n```/;
const match = issueBody.match(base64Regex);
if (!match) {
core.setFailed('No base64 encoded content found in issue body');
return;
}
try {
// Decode base64
const base64Content = match[1].trim();
const compressedBuffer = Buffer.from(base64Content, 'base64');
// Decompress using pako (deflate)
const decompressed = pako.inflate(compressedBuffer, { to: 'string' });
const yamlContent = decompressed;
// Parse YAML to validate and get name
const parsedYaml = yaml.load(yamlContent);
if (!parsedYaml.name) {
core.setFailed('YAML must contain a "name" field');
return;
}
// Sanitize the name: allow lowercase letters and digits only
const rawName = String(parsedYaml.name);
const normalized = rawName.normalize('NFKD').toLowerCase();
if (!normalized.match(/^[a-z0-9]+$/)) {
core.setFailed('Invalid recipe name after sanitization: must contain only lowercase letters and numbers');
return;
}
// Ensure the path stays within the recipes directory
const recipesRoot = path.resolve('recipes');
const recipeDir = path.resolve(recipesRoot, normalized);
if (!recipeDir.startsWith(recipesRoot + path.sep)) {
core.setFailed('Recipe path escapes the recipes directory');
return;
}
// Create recipe directory and write file directly
fs.mkdirSync(recipeDir, { recursive: true });
fs.writeFileSync(path.join(recipeDir, 'build.yaml'), yamlContent);
// Expose sanitized name
core.setOutput('name', normalized);
core.setOutput('parsed', JSON.stringify({ ...parsedYaml, name: normalized }));
} catch (error) {
core.setFailed(`Failed to process encoded content: ${error.message}`);
}
- name: Create branch name
id: create-branch
run: |
set -euo pipefail
BRANCH_NAME="contribution-${{ github.event.issue.number }}-${{ steps.parse-yaml.outputs.name }}"
# Safely write multiline or special characters to GITHUB_OUTPUT
{
echo 'branch_name<<EOF'
echo "$BRANCH_NAME"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Create new branch
run: |
git config --local user.email "[email protected]"
git config --local user.name "GitHub Action"
git checkout -b "${{ steps.create-branch.outputs.branch_name }}"
- name: Check if file exists
id: check-file
run: |
set -euo pipefail
FILE_PATH="recipes/${{ steps.parse-yaml.outputs.name }}/build.yaml"
if [ -f "$FILE_PATH" ]; then
echo "file_exists=true" >> "$GITHUB_OUTPUT"
{ echo 'file_path<<EOF'; echo "$FILE_PATH"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
echo "action=update" >> "$GITHUB_OUTPUT"
else
echo "file_exists=false" >> "$GITHUB_OUTPUT"
{ echo 'file_path<<EOF'; echo "$FILE_PATH"; echo 'EOF'; } >> "$GITHUB_OUTPUT"
echo "action=add" >> "$GITHUB_OUTPUT"
fi
- name: Commit changes
run: |
set -euo pipefail
git add recipes/
# Prepare commit message safely
cat > commitmsg.txt <<'EOF'
${{ steps.check-file.outputs.action == 'add' && 'Add' || 'Update' }} recipe for ${{ steps.parse-yaml.outputs.name }} from issue #${{ github.event.issue.number }}
EOF
AUTHOR="${{ github.event.issue.user.login }} <${{ github.event.issue.user.id }}+${{ github.event.issue.user.login }}@users.noreply.github.com>"
git commit --author="$AUTHOR" -F commitmsg.txt
- name: Push branch
run: |
set -euo pipefail
git push origin "${{ steps.create-branch.outputs.branch_name }}"
- name: Create pull request
id: create-pr
uses: actions/github-script@v7
with:
script: |
const { data: pr } = await github.rest.pulls.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `${{ steps.check-file.outputs.action == 'add' && 'Add' || 'Update' }} recipe for ${{ steps.parse-yaml.outputs.name }}`,
body: `This PR was automatically generated from issue #${{ github.event.issue.number }}.
## Summary
- ${{ steps.check-file.outputs.action == 'add' && 'Adds new' || 'Updates existing' }} recipe for \`${{ steps.parse-yaml.outputs.name }}\`
- Recipe file: \`${{ steps.check-file.outputs.file_path }}\`
Closes #${{ github.event.issue.number }}`,
head: '${{ steps.create-branch.outputs.branch_name }}',
base: 'main'
});
core.setOutput('pr_number', pr.number);
core.setOutput('pr_url', pr.html_url);
- name: Comment on issue with PR link
uses: actions/github-script@v7
with:
script: |
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `✅ A pull request has been automatically created for your contribution!
🔗 Pull Request: #${{ steps.create-pr.outputs.pr_number }}
The recipe for \`${{ steps.parse-yaml.outputs.name }}\` has been ${{ steps.check-file.outputs.action == 'add' && 'added' || 'updated' }} in the PR. A maintainer will review and merge it soon.
Thank you for your contribution! 🎉`
});