Sync Labels #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # GitHub Labels Sync Workflow | |
| # Synchronizes labels from labels.yml to target repositories | |
| # | |
| # This workflow uses github-label-sync to ensure consistent labeling | |
| # across repositories in the organization. | |
| name: Sync Labels | |
| on: | |
| workflow_dispatch: | |
| inputs: | |
| target_repository: | |
| description: 'Target repository (owner/repo format, e.g., epicpast/my-project)' | |
| required: true | |
| type: string | |
| dry_run: | |
| description: 'Dry run mode - preview changes without applying' | |
| required: false | |
| type: boolean | |
| default: true | |
| allow_added_labels: | |
| description: 'Allow labels not in config to remain (do not delete)' | |
| required: false | |
| type: boolean | |
| default: true | |
| schedule: | |
| # Run weekly on Monday at 9:00 AM UTC | |
| - cron: '0 9 * * 1' | |
| # Allow calling from other workflows | |
| workflow_call: | |
| inputs: | |
| target_repository: | |
| description: 'Target repository (owner/repo format)' | |
| required: true | |
| type: string | |
| dry_run: | |
| description: 'Dry run mode' | |
| required: false | |
| type: boolean | |
| default: false | |
| allow_added_labels: | |
| description: 'Allow labels not in config to remain' | |
| required: false | |
| type: boolean | |
| default: true | |
| secrets: | |
| LABEL_SYNC_TOKEN: | |
| description: 'GitHub token with repo scope for label management' | |
| required: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| sync-labels: | |
| name: Sync Labels to Repository | |
| runs-on: ubuntu-latest | |
| # Only run scheduled jobs on the main branch | |
| if: github.event_name != 'schedule' || github.ref == 'refs/heads/main' | |
| env: | |
| # Use environment variables for inputs to prevent injection | |
| TARGET_REPO: ${{ inputs.target_repository }} | |
| DRY_RUN: ${{ inputs.dry_run }} | |
| ALLOW_ADDED: ${{ inputs.allow_added_labels }} | |
| DEFAULT_REPO: ${{ github.repository }} | |
| steps: | |
| - name: Checkout repository | |
| # actions/checkout v6.0.1 - 2025-12-28 | |
| uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 | |
| with: | |
| sparse-checkout: | | |
| labels.yml | |
| sparse-checkout-cone-mode: false | |
| - name: Setup Node.js | |
| # actions/setup-node v6.1.0 - 2025-12-28 | |
| uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f | |
| with: | |
| node-version: '20' | |
| - name: Install dependencies | |
| run: npm install -g [email protected] [email protected] | |
| - name: Validate labels configuration | |
| run: | | |
| echo "Validating labels.yml format..." | |
| export NODE_PATH="$(npm root -g)" | |
| node -e " | |
| const yaml = require('js-yaml'); | |
| const fs = require('fs'); | |
| try { | |
| const labels = yaml.load(fs.readFileSync('labels.yml', 'utf8')); | |
| if (!Array.isArray(labels)) { | |
| console.error('ERROR: labels.yml must be an array'); | |
| process.exit(1); | |
| } | |
| labels.forEach((label, idx) => { | |
| if (!label.name || !label.color) { | |
| console.error('ERROR: Label at index ' + idx + ' missing required fields'); | |
| process.exit(1); | |
| } | |
| }); | |
| console.log('Valid: ' + labels.length + ' labels defined'); | |
| } catch (e) { | |
| console.error('ERROR: ' + e.message); | |
| process.exit(1); | |
| } | |
| " | |
| - name: Determine target repository | |
| id: target | |
| run: | | |
| if [ "${{ github.event_name }}" = "schedule" ]; then | |
| # For scheduled runs, sync to the current repository | |
| echo "repository=${DEFAULT_REPO}" >> "$GITHUB_OUTPUT" | |
| echo "Scheduled sync targeting: ${DEFAULT_REPO}" | |
| else | |
| # Validate repository format (owner/repo) | |
| if ! echo "${TARGET_REPO}" | grep -qE '^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$'; then | |
| echo "ERROR: Invalid repository format. Expected 'owner/repo'" | |
| exit 1 | |
| fi | |
| echo "repository=${TARGET_REPO}" >> "$GITHUB_OUTPUT" | |
| echo "Manual sync targeting: ${TARGET_REPO}" | |
| fi | |
| - name: Sync labels (dry run) | |
| if: env.DRY_RUN == 'true' | |
| run: | | |
| echo "=== DRY RUN MODE ===" | |
| echo "Target: ${SYNC_TARGET}" | |
| echo "" | |
| EXTRA_ARGS="" | |
| if [ "${ALLOW_ADDED}" = "true" ]; then | |
| EXTRA_ARGS="--allow-added-labels" | |
| fi | |
| github-label-sync \ | |
| --access-token "${SYNC_TOKEN}" \ | |
| --labels labels.yml \ | |
| --dry-run \ | |
| ${EXTRA_ARGS} \ | |
| "${SYNC_TARGET}" | |
| env: | |
| SYNC_TOKEN: ${{ secrets.LABEL_SYNC_TOKEN || secrets.GITHUB_TOKEN }} | |
| SYNC_TARGET: ${{ steps.target.outputs.repository }} | |
| NODE_PATH: /usr/local/lib/node_modules | |
| - name: Sync labels (apply) | |
| if: env.DRY_RUN != 'true' | |
| run: | | |
| echo "=== APPLYING LABEL CHANGES ===" | |
| echo "Target: ${SYNC_TARGET}" | |
| echo "" | |
| EXTRA_ARGS="" | |
| if [ "${ALLOW_ADDED}" = "true" ]; then | |
| EXTRA_ARGS="--allow-added-labels" | |
| fi | |
| github-label-sync \ | |
| --access-token "${SYNC_TOKEN}" \ | |
| --labels labels.yml \ | |
| ${EXTRA_ARGS} \ | |
| "${SYNC_TARGET}" | |
| echo "" | |
| echo "Labels synchronized successfully!" | |
| env: | |
| SYNC_TOKEN: ${{ secrets.LABEL_SYNC_TOKEN || secrets.GITHUB_TOKEN }} | |
| SYNC_TARGET: ${{ steps.target.outputs.repository }} | |
| NODE_PATH: /usr/local/lib/node_modules | |
| - name: Summary | |
| run: | | |
| { | |
| echo "## Label Sync Summary" | |
| echo "" | |
| echo "- **Target Repository:** \`${SYNC_TARGET}\`" | |
| echo "- **Mode:** ${MODE_DESC}" | |
| echo "- **Allow Added Labels:** ${ALLOW_ADDED}" | |
| echo "- **Triggered By:** ${TRIGGER}" | |
| echo "" | |
| echo "### Labels Defined" | |
| echo "" | |
| echo "| Category | Count |" | |
| echo "|----------|-------|" | |
| echo "| Priority | 4 |" | |
| echo "| Type | 7 |" | |
| echo "| Status | 5 |" | |
| echo "| Area | 4 |" | |
| echo "| Effort | 3 |" | |
| echo "| **Total** | **23** |" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| env: | |
| SYNC_TARGET: ${{ steps.target.outputs.repository }} | |
| MODE_DESC: ${{ env.DRY_RUN == 'true' && 'Dry Run (preview only)' || 'Applied' }} | |
| TRIGGER: ${{ github.event_name }} |