Skip to content

Remove failed CI card image generation feature and document lessons learned #51

Remove failed CI card image generation feature and document lessons learned

Remove failed CI card image generation feature and document lessons learned #51

Workflow file for this run

name: Build and test Swift
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
pull-requests: write
issues: write
jobs:
test:
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Install SwiftLint
run: brew install swiftlint
- name: Lint
run: make lint
- name: Build
run: make build
- name: Run tests
run: make test
- name: Generate sample card images
run: |
echo "πŸ” Current working directory: $(pwd)"
echo "πŸ” Directory contents before test:"
ls -la
echo "πŸ” Checking if test exists..."
TEST_EXISTS=$(swift test list | grep testGenerateSampleCardImages | wc -l)
echo "πŸ“Š Found $TEST_EXISTS matching tests"
echo "πŸ§ͺ Running image generation test..."
TEST_OUTPUT=$(swift test --filter PlayingCardTests.DisplayCardSnapshotTests.testGenerateSampleCardImages 2>&1)
TEST_EXIT_CODE=$?
echo "$TEST_OUTPUT"
# Check if test actually executed (not just succeeded with 0 tests)
TESTS_EXECUTED=$(echo "$TEST_OUTPUT" | grep "Executed [0-9]* tests" | sed 's/.*Executed \([0-9]*\) tests.*/\1/')
if [ "$TEST_EXIT_CODE" -eq 0 ] && [ "$TESTS_EXECUTED" -gt 0 ] && [ -d "card-images" ]; then
echo "βœ… Test completed successfully and created output"
else
echo "⚠️ Test failed, found 0 tests, or didn't create output - creating bulletproof fallback structure..."
echo "πŸ“Š Test details: exit_code=$TEST_EXIT_CODE, tests_executed=$TESTS_EXECUTED"
# Ensure we're in the right directory
cd "${GITHUB_WORKSPACE}"
echo "πŸ” Fallback working directory: $(pwd)"
# Remove any existing directory to start fresh
rm -rf card-images
# Create directory with explicit error checking
echo "πŸ“ Creating card-images directory..."
if mkdir -p card-images; then
echo "βœ… Directory created successfully"
else
echo "❌ Failed to create directory, attempting alternative..."
mkdir card-images 2>/dev/null || true
fi
# Verify directory exists
if [ -d "card-images" ]; then
echo "βœ… card-images directory confirmed to exist"
else
echo "❌ card-images directory still missing - this is unexpected!"
echo "πŸ” Current directory state:"
ls -la
exit 1
fi
# Create valid minimal PNG files for the expected cards
echo "πŸ–ΌοΈ Creating PNG files..."
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\x0A\x49\x44\x41\x54\x78\x9C\x63\x00\x01\x00\x00\x05\x00\x01\x0D\x0A\x2D\xB4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82' > card-images/spades_A.png || echo "❌ Failed to create spades_A.png"
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\x0A\x49\x44\x41\x54\x78\x9C\x63\x00\x01\x00\x00\x05\x00\x01\x0D\x0A\x2D\xB4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82' > card-images/hearts_K.png || echo "❌ Failed to create hearts_K.png"
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\x0A\x49\x44\x41\x54\x78\x9C\x63\x00\x01\x00\x00\x05\x00\x01\x0D\x0A\x2D\xB4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82' > card-images/diamonds_Q.png || echo "❌ Failed to create diamonds_Q.png"
printf '\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52\x00\x00\x00\x01\x00\x00\x00\x01\x08\x06\x00\x00\x00\x1F\x15\xC4\x89\x00\x00\x00\x0A\x49\x44\x41\x54\x78\x9C\x63\x00\x01\x00\x00\x05\x00\x01\x0D\x0A\x2D\xB4\x00\x00\x00\x00\x49\x45\x4E\x44\xAE\x42\x60\x82' > card-images/clubs_J.png || echo "❌ Failed to create clubs_J.png"
# Create manifest
echo "πŸ“ Creating manifest file..."
cat > card-images/manifest.txt << 'EOF' || echo "❌ Failed to create manifest"
A of spades
K of hearts
Q of diamonds
J of clubs
EOF
echo "βœ… Fallback structure creation completed"
echo "πŸ” Final directory contents:"
ls -la card-images/ || echo "❌ Cannot list card-images directory"
fi
- name: Check for generated images
run: |
echo "Checking for card images directory..."
if [ -d "card-images" ]; then
echo "βœ… card-images directory exists"
echo "Contents:"
ls -la card-images/
else
echo "❌ card-images directory not found"
echo "Current directory contents:"
ls -la
fi
- name: Commit card images to repository
if: always()
run: |
# Configure git for the action
git config --local user.email "action@github.com"
git config --local user.name "GitHub Action"
# Create a temporary branch for images if it doesn't exist
if [ -d "card-images" ]; then
echo "πŸ“Έ Committing generated card images..."
# Add the images to git
git add card-images/
# Only commit if there are changes
if ! git diff --cached --quiet; then
git commit -m "Add generated card images for PR review [skip ci]"
git push origin HEAD
echo "βœ… Images committed and pushed"
else
echo "ℹ️ No new images to commit"
fi
else
echo "⚠️ No card-images directory found to commit"
fi
- name: Upload card images as artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: sample-card-images
path: card-images/
retention-days: 30
- name: Comment PR with embedded card images
if: github.event_name == 'pull_request' && always()
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const path = require('path');
console.log('πŸ” Starting PR comment generation...');
// Check if card images directory exists
const cardImagesDir = 'card-images';
if (!fs.existsSync(cardImagesDir)) {
console.log('❌ No card images directory found');
// Check current directory to debug
console.log('πŸ“ Current directory contents:');
fs.readdirSync('.').forEach(file => console.log(` - ${file}`));
// Post a comment about the failure
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: `## ⚠️ Card Image Generation Failed\n\nThe workflow attempted to generate sample playing card images but the output directory was not created. This may indicate an issue with the test execution or CI environment.\n\n**Debug Info**: Generated at ${new Date().toISOString()}`
});
return;
}
console.log('βœ… Found card-images directory');
// Get list of generated files (including manifest)
const allFiles = fs.readdirSync(cardImagesDir).sort();
const imageFiles = allFiles.filter(file => file.endsWith('.png'));
const manifestFile = allFiles.find(file => file === 'manifest.txt');
console.log(`πŸ“‹ Found ${allFiles.length} total files: ${allFiles.join(', ')}`);
console.log(`πŸ–ΌοΈ Found ${imageFiles.length} PNG files: ${imageFiles.join(', ')}`);
// Check if these are fallback images (minimal 1x1 PNG files)
let hasFallbackImages = false;
if (imageFiles.length > 0) {
const firstImagePath = path.join(cardImagesDir, imageFiles[0]);
const firstImageStats = fs.statSync(firstImagePath);
// Fallback images are exactly 67 bytes (minimal PNG)
if (firstImageStats.size === 67) {
hasFallbackImages = true;
console.log('πŸ”„ Detected fallback PNG files (minimal size)');
} else {
console.log(`πŸ“ Real images detected (${firstImageStats.size} bytes)`);
}
}
// Read manifest for card information
let cardList = [];
let cardImageMap = new Map(); // Map card descriptions to filenames
if (manifestFile) {
const manifestPath = path.join(cardImagesDir, manifestFile);
const manifestContent = fs.readFileSync(manifestPath, 'utf8');
cardList = manifestContent.trim().split('\n').filter(line => line.trim());
console.log(`πŸ“ Manifest contains ${cardList.length} cards: ${cardList.join(', ')}`);
// Create mapping from card description to image filename
cardList.forEach(cardInfo => {
// Convert card description to expected filename
const parts = cardInfo.split(' of ');
if (parts.length === 2) {
const rank = parts[0].toLowerCase();
const suit = parts[1].toLowerCase();
const expectedFilename = `${suit}_${rank}.png`;
if (imageFiles.includes(expectedFilename)) {
cardImageMap.set(cardInfo, expectedFilename);
}
}
});
}
// Create comment body
let commentBody = `## πŸƒ Sample Playing Cards Generated\n\n`;
if (hasFallbackImages) {
commentBody += `⚠️ **Note**: Card images were generated using fallback mode due to SwiftUI rendering limitations in the CI environment. The images below are placeholders but demonstrate the workflow functionality.\n\n`;
} else {
commentBody += `The following sample cards were generated from the \`DisplayCard\` SwiftUI component:\n\n`;
}
const suitEmojis = {
'spades': '♠️',
'hearts': 'β™₯️',
'diamonds': '♦️',
'clubs': '♣️'
};
// Get the current branch name for image URLs
const currentBranch = context.ref.replace('refs/heads/', '');
const repoUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}`;
if (cardList.length > 0 && imageFiles.length > 0) {
// Embed actual images (or placeholders)
for (const cardInfo of cardList) {
// Try to add appropriate emoji based on suit
let displayName = cardInfo;
for (const [suit, emoji] of Object.entries(suitEmojis)) {
if (cardInfo.toLowerCase().includes(suit)) {
displayName = `${cardInfo} ${emoji}`;
break;
}
}
const imageFilename = cardImageMap.get(cardInfo);
if (imageFilename) {
// Embed the image using GitHub's raw content URL
const imageUrl = `${repoUrl}/raw/${currentBranch}/card-images/${imageFilename}`;
commentBody += `### ${displayName}\n`;
if (hasFallbackImages) {
commentBody += `![${cardInfo} placeholder](${imageUrl}) *(Placeholder image)*\n\n`;
} else {
commentBody += `![${cardInfo}](${imageUrl})\n\n`;
}
} else {
commentBody += `### ${displayName}\n`;
commentBody += `*Image generation failed for this card*\n\n`;
}
}
} else if (cardList.length > 0) {
// Fallback to text list when no images available
for (const cardInfo of cardList) {
let displayName = cardInfo;
for (const [suit, emoji] of Object.entries(suitEmojis)) {
if (cardInfo.toLowerCase().includes(suit)) {
displayName = `${cardInfo} ${emoji}`;
break;
}
}
commentBody += `- ${displayName} *(image not available)*\n`;
}
} else {
commentBody += `Card generation completed but no valid cards were found.\n\n`;
}
if (imageFiles.length > 0) {
if (hasFallbackImages) {
commentBody += `\n**Format**: ${imageFiles.length} placeholder PNG image(s) created due to CI limitations\n`;
} else {
commentBody += `\n**Format**: ${imageFiles.length} PNG image(s) with enhanced rendering for better quality\n`;
}
} else {
commentBody += `\n**Note**: Images could not be generated due to CI environment limitations\n`;
}
commentBody += `\nπŸ“ **Artifacts**: The generated files are also available as artifacts in the [Actions tab](${context.payload.repository.html_url}/actions/runs/${context.runId}).\n\n`;
if (hasFallbackImages) {
commentBody += `**Development Note**: This workflow runs on macOS CI where SwiftUI rendering should work properly. The fallback mode indicates an issue with the test environment.\n\n`;
} else {
commentBody += `These help reviewers visualize how the \`DisplayCard\` SwiftUI component renders different playing cards.\n\n`;
}
commentBody += `**Generated**: ${allFiles.length} file(s) at ${new Date().toISOString()}`;
console.log('πŸ’¬ Posting PR comment with embedded images...');
// Post comment on PR
await github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: commentBody
});
console.log('βœ… Successfully posted PR comment with embedded card images');