Skip to content

Create Release

Create Release #12

Workflow file for this run

name: Create Release
on:
workflow_dispatch:
inputs:
bump:
type: choice
description: 'Semver bump type'
options: [patch, minor, major]
default: patch
custom_version:
description: 'Custom version (optional, overrides bump)'
required: false
type: string
release_type:
type: choice
description: 'Release type'
options: [stable, beta]
default: stable
permissions:
contents: write # commit version bump + tag
jobs:
build-package:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm install
- name: Install Salesforce CLI
run: |
npm install -g @salesforce/cli
echo "$(npm config get prefix)/bin" >> $GITHUB_PATH
sf --version
- name: Auth via JWT (packaging org)
env:
SFDX_CLIENT_ID: ${{ secrets.SF_CLIENT_ID }}
SFDX_JWT_KEY: ${{ secrets.SF_JWT_KEY }}
SF_USERNAME: ${{ secrets.SF_PACKAGING_USERNAME }}
run: |
echo "$SFDX_JWT_KEY" > server.key
sf org login jwt \
--client-id "$SFDX_CLIENT_ID" \
--jwt-key-file server.key \
--username "$SF_USERNAME" \
--alias pkgorg --set-default
rm -f server.key
- name: Read and calculate version
id: version
run: |
# Read current version from package.json
CUR=$(node -p "require('./package.json').version")
MAJOR=$(echo "$CUR" | cut -d. -f1)
MINOR=$(echo "$CUR" | cut -d. -f2)
PATCH=$(echo "$CUR" | cut -d. -f3)
if [ -n "${{ github.event.inputs.custom_version }}" ]; then
NEW="${{ github.event.inputs.custom_version }}"
echo "Using custom version: $NEW"
else
case "${{ github.event.inputs.bump }}" in
major) MAJOR=$((MAJOR+1)); MINOR=0; PATCH=0 ;;
minor) MINOR=$((MINOR+1)); PATCH=0 ;;
patch) PATCH=$((PATCH+1)) ;;
esac
NEW="$MAJOR.$MINOR.$PATCH"
fi
# Add beta suffix if release type is beta
if [ "${{ github.event.inputs.release_type }}" == "beta" ]; then
NEW="${NEW}-beta"
fi
echo "current=$CUR" >> $GITHUB_OUTPUT
echo "new=$NEW" >> $GITHUB_OUTPUT
echo "tag=v$NEW" >> $GITHUB_OUTPUT
echo "release_type=${{ github.event.inputs.release_type }}" >> $GITHUB_OUTPUT
echo "Current version: $CUR"
echo "New version: $NEW"
echo "Release type: ${{ github.event.inputs.release_type }}"
- name: Update package.json version
run: |
NEW_VERSION="${{ steps.version.outputs.new }}"
npm version $NEW_VERSION --no-git-tag-version
echo "Updated package.json to version $NEW_VERSION"
- name: Update package.xml version
run: |
NEW_VERSION="${{ steps.version.outputs.new }}"
API_VERSION=$(echo $NEW_VERSION | cut -d. -f1)
sed -i "s/<version>.*<\/version>/<version>$API_VERSION.0<\/version>/" manifest/package.xml
echo "Updated package.xml API version to $API_VERSION.0"
- name: Update sfdx-project.json version
run: |
NEW_VERSION="${{ steps.version.outputs.new }}"
# Update versionName in sfdx-project.json
sed -i "s/\"versionName\": \"ver [^\"]*\"/\"versionName\": \"ver $NEW_VERSION\"/" sfdx-project.json
# Update versionNumber in sfdx-project.json (format: MAJOR.MINOR.PATCH.NEXT)
sed -i "s/\"versionNumber\": \"[^\"]*\"/\"versionNumber\": \"$NEW_VERSION.NEXT\"/" sfdx-project.json
echo "Updated sfdx-project.json version to $NEW_VERSION"
- name: Create package
run: |
# Create package directory
mkdir -p package
# Copy source files
cp -r force-app package/
cp manifest/package.xml package/
cp README.md package/
cp docs/PACKAGE-README.md package/ 2>/dev/null || true
# Create package zip
cd package
zip -r ../REST-API-Library-v${{ steps.version.outputs.new }}.zip .
cd ..
echo "Created REST-API-Library-v${{ steps.version.outputs.new }}.zip"
# Clean up package directory
rm -rf package
echo "Cleaned up temporary package directory"
- name: Convert to MDAPI
run: |
sf project convert source --root-dir force-app --output-dir mdapi_out
- name: Run tests and check code coverage
run: |
echo "Running tests for package-specific test classes: NebulaAdapter_Test, RestLibTests"
# Run only the test classes that are part of this package
set +e # Don't exit on error, we'll handle it manually
sf apex test run --class-names NebulaAdapter_Test,RestLibTests --target-org pkgorg --result-format json --code-coverage --wait 10 > test_result.json 2>&1
TEST_EXIT_CODE=$?
set -e # Re-enable exit on error
echo "Test execution exit code: $TEST_EXIT_CODE"
echo "Test execution output:"
# Read from file to avoid broken pipe issues
cat test_result.json
# Check if test execution was successful
if [ $TEST_EXIT_CODE -ne 0 ]; then
echo "❌ ERROR: Test execution failed with exit code $TEST_EXIT_CODE"
echo "Raw output:"
cat test_result.json
exit 1
fi
# Check if we have valid JSON output
if ! jq -e '.result' test_result.json > /dev/null 2>&1; then
echo "❌ ERROR: Invalid test result format"
echo "Raw output:"
cat test_result.json
exit 1
fi
# Extract test summary
if jq -e '.result.summary' test_result.json > /dev/null 2>&1; then
SUMMARY=$(jq -r '.result.summary' test_result.json)
echo "Test Summary: $SUMMARY"
# Show detailed test counts
PASSING=$(jq -r '.result.summary.passing // 0' test_result.json)
FAILING=$(jq -r '.result.summary.failing // 0' test_result.json)
SKIPPED=$(jq -r '.result.summary.skipped // 0' test_result.json)
TESTS_RAN=$(jq -r '.result.summary.testsRan // 0' test_result.json)
echo "📈 Test Results: $PASSING passed, $FAILING failed, $SKIPPED skipped (Total: $TESTS_RAN)"
fi
# Extract coverage percentage - try multiple possible locations in the JSON
COVERAGE=""
if jq -e '.result.summary.testRunCoverage' test_result.json > /dev/null 2>&1; then
COVERAGE=$(jq -r '.result.summary.testRunCoverage' test_result.json | sed 's/%//')
echo "Code coverage (test run): $COVERAGE%"
elif jq -e '.result.coverage.coverage' test_result.json > /dev/null 2>&1; then
COVERAGE=$(jq -r '.result.coverage.coverage[].percent' test_result.json)
echo "Code coverage (per class): $COVERAGE%"
else
echo "⚠️ WARNING: Could not extract coverage information from test results"
fi
# Check if coverage meets minimum requirement (75%)
if [ -n "$COVERAGE" ]; then
MIN_COVERAGE=75
if (( $(echo "$COVERAGE < $MIN_COVERAGE" | bc -l) )); then
echo ""
echo "❌ COVERAGE FAILURE: Code coverage $COVERAGE% is below minimum requirement of $MIN_COVERAGE%"
echo ""
echo "📊 COVERAGE ANALYSIS:"
echo " Current Coverage: $COVERAGE%"
echo " Required Coverage: $MIN_COVERAGE%"
echo " Coverage Gap: $((MIN_COVERAGE - COVERAGE))%"
echo ""
echo "🔍 TEST CLASSES BEING VALIDATED:"
echo " - NebulaAdapter_Test"
echo " - RestLibTests"
echo ""
echo "💡 TO FIX THIS ISSUE:"
echo " 1. Add more test methods to cover uncovered code paths"
echo " 2. Improve existing test methods to cover more scenarios"
echo " 3. Review uncovered lines in the test results above"
echo " 4. Ensure all public methods and critical code paths are tested"
echo ""
echo "📋 NEXT STEPS:"
echo " - Check the detailed coverage information in the test output above"
echo " - Look for 'uncoveredLines' in the JSON output to see which lines need testing"
echo " - Add test methods for any missing scenarios"
echo " - Re-run the workflow after improving test coverage"
echo ""
exit 1
else
echo "✅ SUCCESS: Code coverage $COVERAGE% meets minimum requirement of $MIN_COVERAGE%"
fi
fi
# Check for test failures
if jq -e '.result.failures' test_result.json > /dev/null 2>&1; then
FAILURES=$(jq -r '.result.failures' test_result.json)
if [ "$FAILURES" != "0" ] && [ "$FAILURES" != "null" ]; then
echo "❌ ERROR: $FAILURES test(s) failed"
echo "Test failures details:"
jq -r '.result.tests[] | select(.Outcome == "Fail") | " - \(.MethodName): \(.Message)"' test_result.json 2>/dev/null || echo "Could not extract failure details"
exit 1
fi
fi
# Clean up temporary test result file
rm -f test_result.json
- name: Deploy to packaging org
run: |
echo "Deploying converted metadata to packaging org..."
echo "Source directory: mdapi_out"
echo "Target org: pkgorg"
set +e # Don't exit on error, we'll handle it manually
DEPLOY_OUTPUT=$(sf project deploy start --metadata-dir mdapi_out --target-org pkgorg --wait 60 --ignore-conflicts 2>&1)
DEPLOY_EXIT_CODE=$?
set -e # Re-enable exit on error
echo "Deploy exit code: $DEPLOY_EXIT_CODE"
echo "Deploy output:"
echo "$DEPLOY_OUTPUT"
if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
echo "✅ Successfully deployed to packaging org"
else
echo "❌ ERROR: Deployment failed with exit code $DEPLOY_EXIT_CODE"
echo "💡 Check the output above for specific error details"
exit 1
fi
- name: Create package and version
id: package_version
env:
SF_PACKAGE1_ID: ${{ vars.SF_PACKAGE1_ID }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Check if package ID exists, if not create a new package
if [ -z "$SF_PACKAGE1_ID" ] || [ "$SF_PACKAGE1_ID" = "NULL" ] || [ "$SF_PACKAGE1_ID" = "null" ] || [ "$SF_PACKAGE1_ID" = "" ]; then
echo "No package ID found, creating new package..."
# Debug: Check authentication and org status
echo "Checking authentication..."
sf org list || echo "No orgs found"
# Debug: Check if we're in the right org
echo "Current org info:"
sf org display || echo "No org display available"
# Debug: Try a simple command first
echo "Testing basic sf command..."
sf --version || echo "SF CLI not working"
# Create a new package with more debugging
echo "Creating package with command:"
echo "sf package create --name 'REST API Library' --description 'A comprehensive Salesforce Apex library for making REST API callouts' --package-type Unlocked --path force-app --target-dev-hub pkgorg --json"
PACKAGE_OUTPUT=$(sf package create \
--name "REST API Library" \
--description "A comprehensive Salesforce Apex library for making REST API callouts" \
--package-type Unlocked \
--path force-app \
--target-dev-hub pkgorg \
--json 2>&1)
echo "Package create exit code: $?"
echo "Package create output:"
echo "$PACKAGE_OUTPUT"
# Extract package ID
PACKAGE_ID=$(echo "$PACKAGE_OUTPUT" | jq -r '.result.Id')
if [ "$PACKAGE_ID" != "null" ] && [ -n "$PACKAGE_ID" ]; then
echo "✅ Created new package with ID: $PACKAGE_ID"
# Update GitHub repository variable automatically
echo "Updating GitHub repository variable..."
curl -X PATCH \
-H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GITHUB_TOKEN" \
-H "X-GitHub-Api-Version: 2022-11-28" \
"https://api.github.com/repos/${{ github.repository }}/actions/variables/SF_PACKAGE1_ID" \
-d "{\"name\":\"SF_PACKAGE1_ID\",\"value\":\"$PACKAGE_ID\"}" && echo "✅ GitHub variable updated successfully" || echo "⚠️ Failed to update GitHub variable (you can update manually)"
# Set the package ID for this run
SF_PACKAGE1_ID="$PACKAGE_ID"
else
echo "ERROR: Failed to create package"
echo "Full output: $PACKAGE_OUTPUT"
exit 1
fi
else
echo "Using existing package ID: $SF_PACKAGE1_ID"
fi
# Create a new package version
echo "Creating package version for package: $SF_PACKAGE1_ID"
echo "Command: sf package version create --package '$SF_PACKAGE1_ID' --installation-key-bypass --wait 10 --target-dev-hub pkgorg --json"
# Run the command and capture both output and exit code
set +e # Don't exit on error, we'll handle it manually
PACKAGE_VERSION_OUTPUT=$(sf package version create \
--package "$SF_PACKAGE1_ID" \
--installation-key-bypass \
--wait 10 \
--target-dev-hub pkgorg \
--json 2>&1)
PACKAGE_VERSION_EXIT_CODE=$?
set -e # Re-enable exit on error
echo "Package version create exit code: $PACKAGE_VERSION_EXIT_CODE"
echo "Package version create output:"
echo "$PACKAGE_VERSION_OUTPUT"
# Check if the command was successful
if [ $PACKAGE_VERSION_EXIT_CODE -eq 0 ] && echo "$PACKAGE_VERSION_OUTPUT" | jq -e '.result.SubscriberPackageVersionId' > /dev/null 2>&1; then
# Extract the package version ID and create installation URLs
PACKAGE_VERSION_ID=$(echo "$PACKAGE_VERSION_OUTPUT" | jq -r '.result.SubscriberPackageVersionId')
PRODUCTION_URL="https://login.salesforce.com/packaging/installPackage.apexp?p0=$PACKAGE_VERSION_ID"
SANDBOX_URL="https://test.salesforce.com/packaging/installPackage.apexp?p0=$PACKAGE_VERSION_ID"
echo "package_version_id=$PACKAGE_VERSION_ID" >> $GITHUB_OUTPUT
echo "production_url=$PRODUCTION_URL" >> $GITHUB_OUTPUT
echo "sandbox_url=$SANDBOX_URL" >> $GITHUB_OUTPUT
echo "package_id=$SF_PACKAGE1_ID" >> $GITHUB_OUTPUT
echo "✅ Created package version: $PACKAGE_VERSION_ID"
echo "Production URL: $PRODUCTION_URL"
echo "Sandbox URL: $SANDBOX_URL"
else
echo "❌ ERROR: Failed to create package version"
echo "Exit code: $PACKAGE_VERSION_EXIT_CODE"
echo "Full output: $PACKAGE_VERSION_OUTPUT"
# Try to extract error details from JSON output
if echo "$PACKAGE_VERSION_OUTPUT" | jq -e '.message' > /dev/null 2>&1; then
ERROR_MESSAGE=$(echo "$PACKAGE_VERSION_OUTPUT" | jq -r '.message')
echo "Error message: $ERROR_MESSAGE"
fi
if echo "$PACKAGE_VERSION_OUTPUT" | jq -e '.result[0].error' > /dev/null 2>&1; then
ERROR_DETAILS=$(echo "$PACKAGE_VERSION_OUTPUT" | jq -r '.result[0].error')
echo "Error details: $ERROR_DETAILS"
fi
# Check for common issues
if echo "$PACKAGE_VERSION_OUTPUT" | grep -i "permission" > /dev/null; then
echo "💡 Possible permission issue - check if the user has Package Creation permissions"
fi
if echo "$PACKAGE_VERSION_OUTPUT" | grep -i "validation" > /dev/null; then
echo "💡 Possible validation error - check if all metadata is valid"
fi
if echo "$PACKAGE_VERSION_OUTPUT" | grep -i "coverage" > /dev/null; then
echo "💡 Possible code coverage issue - ensure all code has adequate test coverage"
fi
exit 1
fi
- name: Build changelog
run: |
# Get previous tag
git fetch --tags --quiet || true
PREV=$(git tag --sort=-creatordate | head -1)
if [ -n "$PREV" ]; then
RANGE="$PREV..HEAD"
COMPARE_LINK="https://github.com/${{ github.repository }}/compare/$PREV...${{ steps.version.outputs.tag }}"
else
FIRST=$(git rev-list --max-parents=0 HEAD)
RANGE="$FIRST..HEAD"
COMPARE_LINK="https://github.com/${{ github.repository }}/compare/$FIRST...${{ steps.version.outputs.tag }}"
fi
{
echo "## Release ${{ steps.version.outputs.tag }}"
echo
echo "**Package:** REST-API-Library-v${{ steps.version.outputs.new }}.zip"
echo "**Previous Version:** ${{ steps.version.outputs.current }}"
echo "**Release Type:** ${{ steps.version.outputs.release_type }}"
echo "**Package Version ID:** ${{ steps.package_version.outputs.package_version_id }}"
echo
echo "### Installation"
echo "- **Production:** [${{ steps.package_version.outputs.production_url }}](${{ steps.package_version.outputs.production_url }})"
echo "- **Sandbox:** [${{ steps.package_version.outputs.sandbox_url }}](${{ steps.package_version.outputs.sandbox_url }})"
echo "- **CLI:** \`sf package install --package ${{ steps.package_version.outputs.package_version_id }} --wait 10 --installation-key-bypass\`"
echo
echo "### Changes"
if [ -n "$PREV" ]; then
git log --pretty=format:'- %s (%h) — %an' $RANGE
else
echo "- Initial release"
fi
echo
echo "[Compare changes](${COMPARE_LINK})"
} > release_notes.md
echo "Generated changelog from $RANGE"
- name: Commit version changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: github-actions
GIT_AUTHOR_EMAIL: github-actions@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: github-actions@users.noreply.github.com
run: |
set -e
git add package.json manifest/package.xml sfdx-project.json
if ! git diff --quiet --staged; then
git commit -m "chore(release): bump version to ${{ steps.version.outputs.new }}"
fi
git tag -a "${{ steps.version.outputs.tag }}" -m "Release ${{ steps.version.outputs.new }}"
git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} HEAD:${{ github.ref_name }}
git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} "${{ steps.version.outputs.tag }}"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: release-${{ steps.version.outputs.tag }}
path: |
REST-API-Library-v${{ steps.version.outputs.new }}.zip
release_notes.md
retention-days: 30
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ steps.version.outputs.tag }}
name: REST API Library ${{ steps.version.outputs.tag }} (${{ steps.version.outputs.release_type }})
body_path: release_notes.md
files: |
REST-API-Library-v${{ steps.version.outputs.new }}.zip
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update README with release info
run: |
PACKAGE_FILE="REST-API-Library-v${{ steps.version.outputs.new }}.zip"
{
echo "<!--LATEST-RELEASE-START-->"
echo "**Version:** ${{ steps.version.outputs.new }} "
echo "**Tag:** ${{ steps.version.outputs.tag }} "
echo "**Release Type:** ${{ steps.version.outputs.release_type }} "
echo "**Package Version ID:** ${{ steps.package_version.outputs.package_version_id }} "
echo "**Package:** [$PACKAGE_FILE](https://github.com/${{ github.repository }}/releases/download/${{ steps.version.outputs.tag }}/$PACKAGE_FILE) "
echo "**Release Date:** $(date -u +"%Y-%m-%d") "
echo ""
echo "### Quick Install"
echo "- **Production:** [${{ steps.package_version.outputs.production_url }}](${{ steps.package_version.outputs.production_url }})"
echo "- **Sandbox:** [${{ steps.package_version.outputs.sandbox_url }}](${{ steps.package_version.outputs.sandbox_url }})"
echo "- **CLI:** \`sf package install --package ${{ steps.package_version.outputs.package_version_id }} --wait 10 --installation-key-bypass\`"
echo ""
echo "<details><summary>Change summary</summary>"
echo ""
# Skip first 5 lines (header) from notes if desired:
awk 'NR>5 {print}' release_notes.md
echo "</details>"
echo "<!--LATEST-RELEASE-END-->"
} > latest_block.md
# Update README with the new block (replace entire section)
awk '
BEGIN{printing=1; in_section=0}
/<!--LATEST-RELEASE-START-->/ {print; system("cat latest_block.md"); printing=0; in_section=1; next}
/<!--LATEST-RELEASE-END-->/ {print; printing=1; in_section=0; next}
in_section==0 && printing==1 {print}
' README.md > README.md.new
mv README.md.new README.md
rm latest_block.md
echo "Updated README.md with latest release information"
- name: Commit README update
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GIT_AUTHOR_NAME: github-actions
GIT_AUTHOR_EMAIL: github-actions@users.noreply.github.com
GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: github-actions@users.noreply.github.com
run: |
set -e
if ! git diff --quiet; then
git add README.md
git commit -m "docs: update README for ${{ steps.version.outputs.tag }}"
git push https://x-access-token:${GITHUB_TOKEN}@github.com/${{ github.repository }} HEAD:${{ github.ref_name }}
echo "Committed README update"
else
echo "No README changes to commit"
fi
- name: Summary
run: |
echo "## Release Created Successfully!" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Version:** ${{ steps.version.outputs.new }}" >> $GITHUB_STEP_SUMMARY
echo "**Previous Version:** ${{ steps.version.outputs.current }}" >> $GITHUB_STEP_SUMMARY
echo "**Release Type:** ${{ steps.version.outputs.release_type }}" >> $GITHUB_STEP_SUMMARY
echo "**Bump Type:** ${{ github.event.inputs.bump }}" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Artifacts Created" >> $GITHUB_STEP_SUMMARY
echo "- Package zip file" >> $GITHUB_STEP_SUMMARY
echo "- Salesforce package version: ${{ steps.package_version.outputs.package_version_id }}" >> $GITHUB_STEP_SUMMARY
echo "- Production URL: ${{ steps.package_version.outputs.production_url }}" >> $GITHUB_STEP_SUMMARY
echo "- Sandbox URL: ${{ steps.package_version.outputs.sandbox_url }}" >> $GITHUB_STEP_SUMMARY
echo "- Release notes" >> $GITHUB_STEP_SUMMARY
echo "- Version file" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Next Steps" >> $GITHUB_STEP_SUMMARY
echo "- GitHub release created and README updated automatically!" >> $GITHUB_STEP_SUMMARY
echo "- Package version created and ready for installation!" >> $GITHUB_STEP_SUMMARY