如何通过Drone CI/CD流水线发布PHP Composer包并规避风险?
Got it, let's break down how to safely publish your PHP Composer package with Drone CI, and fix that frustrating issue where a Git tag might slip through even if your tests fail.
The Core Problem
Composer relies entirely on Git tags to resolve package versions, but the usual workflow (manually tagging first, then letting CI run) has a big flaw: if your tests fail after you've pushed the tag, that tag's already out there—Composer can pick it up before you have a chance to roll it back. The fix? Shift the tag creation and publishing to happen only after your CI pipeline passes all tests.
Step-by-Step Implementation
1. Structure Your Drone Pipeline
Split your .drone.yml into two main stages: one for testing, and one for publishing that only runs when tests pass. Here's a basic skeleton:
kind: pipeline type: docker name: default steps: # Step 1: Run your PHP tests (PHPUnit, Psalm, etc.) - name: test image: php:8.2-cli commands: - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer - composer install --no-dev - ./vendor/bin/phpunit # Step 2: Publish (only runs if tests pass) - name: publish image: php:8.2-cli commands: # We'll fill these in next when: branch: main # Only run on your main branch status: success # Only run if all previous steps passed
2. Add Git Tagging Logic to the Publish Step
Instead of manually tagging, let Drone create and push the tag only after tests pass. You'll need to handle versioning—here are two common approaches:
Option A: Semantic Versioning via Commit Messages
Use a tool like semver to parse commit messages (e.g., feat: add X triggers a minor version bump) and generate the new tag:
- name: publish image: php:8.2-cli environment: GIT_USERNAME: from_secret: git_username GIT_PASSWORD: from_secret: git_password commands: # Install dependencies for tagging - apt-get update && apt-get install -y git curl - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer # Install semver tool - curl -L https://github.com/fsaintjacques/semver-tool/releases/download/3.4.0/semver > /usr/local/bin/semver && chmod +x /usr/local/bin/semver # Fetch all existing tags to calculate the next version - git fetch --tags # Get the latest tag, default to v0.0.0 if none exist - LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo v0.0.0) # Bump version based on commit message - COMMIT_MSG=$(git log -1 --pretty=%B) - if echo "$COMMIT_MSG" | grep -q "BREAKING CHANGE"; then NEW_TAG=$(semver bump major $LAST_TAG); fi - if echo "$COMMIT_MSG" | grep -q "^feat:"; then NEW_TAG=$(semver bump minor $LAST_TAG); fi - if echo "$COMMIT_MSG" | grep -q "^fix:"; then NEW_TAG=$(semver bump patch $LAST_TAG); fi # Fallback to patch if no matching message - NEW_TAG=${NEW_TAG:-$(semver bump patch $LAST_TAG)} # Configure Git for tagging - git config --global user.name "$GIT_USERNAME" - git config --global user.email "drone@yourdomain.com" # Check if tag exists to avoid duplicates - if git rev-parse --verify $NEW_TAG >/dev/null 2>&1; then echo "Tag $NEW_TAG already exists, skipping"; exit 0; fi # Create and push the tag - git tag $NEW_TAG - git push https://$GIT_USERNAME:$GIT_PASSWORD@your-git-repo-url.com/your/package.git $NEW_TAG
Option B: Manual Version Input
If you prefer to specify the version explicitly (e.g., via a Drone secret), simplify the tagging step:
- name: publish image: php:8.2-cli environment: GIT_USERNAME: from_secret: git_username GIT_PASSWORD: from_secret: git_password PACKAGE_VERSION: from_secret: package_version commands: - apt-get update && apt-get install -y git - git config --global user.name "$GIT_USERNAME" - git config --global user.email "drone@yourdomain.com" - TAG_NAME="v$PACKAGE_VERSION" - if git rev-parse --verify $TAG_NAME >/dev/null 2>&1; then echo "Tag $TAG_NAME already exists, skipping"; exit 0; fi - git tag $TAG_NAME - git push https://$GIT_USERNAME:$GIT_PASSWORD@your-git-repo-url.com/your/package.git $TAG_NAME
3. Publish to Composer Repository
Once the tag is pushed, trigger an update for your Composer repository. For Packagist, use their API to sync the new tag:
- name: publish # ... previous tagging commands ... commands: # ... tagging commands ... # Trigger Packagist update - curl -X POST -H "Content-Type: application/json" -d '{"repository":{"url":"https://your-git-repo-url.com/your/package.git"}}' https://packagist.org/api/update-package?username=your-packagist-username&apiToken=${PACKAGIST_TOKEN} environment: # ... other secrets ... PACKAGIST_TOKEN: from_secret: packagist_token
For private Composer repos, adjust the command to match your repo's sync mechanism (e.g., a custom API endpoint or local repository update script).
Key Safeguards
- Restrict to main branch: Use
when: branch: mainto avoid publishing untested feature branches. - Duplicate tag check: Always verify the tag doesn't exist before creating it to prevent redundant publishes.
- Secure secrets: Store all credentials (Git, Packagist tokens) as Drone secrets—never hardcode them in your pipeline file.
Why This Works
By moving tag creation and publishing to the post-test stage, you guarantee that only code passing all tests gets tagged and made available to Composer. No more broken versions slipping through because you tagged too early.
内容的提问来源于stack exchange,提问作者AndrewMcLagan

