diff --git a/.github/scripts/trigger-package-releases.sh b/.github/scripts/trigger-package-releases.sh new file mode 100755 index 00000000..94595030 --- /dev/null +++ b/.github/scripts/trigger-package-releases.sh @@ -0,0 +1,53 @@ +#!/bin/bash +set -e + +# Script to trigger release-publish workflows for all unpublished packages +# This script is used by the release-tag workflow after tags are created + +echo "Starting package release workflow triggers..." + +# Check if GH_TOKEN is set +if [ -z "$GH_TOKEN" ]; then + echo "Error: GH_TOKEN environment variable is not set" + exit 1 +fi + +# Counter for tracking +TOTAL_PACKAGES=0 +SUCCESS_COUNT=0 +FAILED_PACKAGES=() + +# Run melos exec to trigger workflows for each package +# --no-published: Only unpublished packages +# --no-private: Exclude private packages +# --order-dependents: Process in dependency order +# -c 1: Run one at a time (concurrency 1) +echo "Triggering workflows for unpublished packages..." +melos exec \ + -c 1 \ + --no-published \ + --no-private \ + --order-dependents \ + -- bash -c ' + PACKAGE_NAME="${MELOS_PACKAGE_NAME}" + PACKAGE_VERSION="${MELOS_PACKAGE_VERSION}" + REF="${PACKAGE_NAME}-v${PACKAGE_VERSION}" + + echo "----------------------------------------" + echo "Processing: $PACKAGE_NAME v$PACKAGE_VERSION" + echo "Ref: $REF" + + if gh workflow run release-publish.yml --ref "$REF"; then + echo "✓ Successfully triggered workflow for $PACKAGE_NAME" + exit 0 + else + echo "✗ Failed to trigger workflow for $PACKAGE_NAME" + exit 1 + fi + ' || { + echo "Error: Some package workflows failed to trigger" + exit 1 + } + +echo "----------------------------------------" +echo "All package release workflows triggered successfully!" diff --git a/.github/workflows/dependency-scan.yml b/.github/workflows/dependency-scan.yml new file mode 100644 index 00000000..68c2a2ce --- /dev/null +++ b/.github/workflows/dependency-scan.yml @@ -0,0 +1,149 @@ +name: Dependency Vulnerability Scan + +on: + schedule: + # Run weekly on Mondays at 9:00 UTC + - cron: '0 9 * * 1' + workflow_dispatch: + pull_request: + paths: + - '**/pubspec.yaml' + - '**/pubspec.lock' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + security-events: write + issues: write + +jobs: + scan-dependencies: + name: Scan Dependencies for Vulnerabilities + runs-on: ubuntu-latest + timeout-minutes: 15 + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Dart + uses: dart-lang/setup-dart@v1 + with: + sdk: stable + + - name: Setup Flutter + uses: subosito/flutter-action@v2 + with: + cache: true + + - name: Install Melos + run: dart pub global activate melos + + - name: Bootstrap workspace + run: melos bootstrap + + - name: Check for outdated dependencies + id: outdated + continue-on-error: true + run: | + echo "## Outdated Dependencies Report" > outdated_report.md + echo "" >> outdated_report.md + + # Check each package for outdated dependencies + for package in packages/*/pubspec.yaml; do + package_dir=$(dirname "$package") + package_name=$(basename "$package_dir") + + echo "### Package: $package_name" >> outdated_report.md + echo "" >> outdated_report.md + + cd "$package_dir" + if dart pub outdated --json > outdated.json 2>/dev/null; then + # Parse and format the outdated dependencies + if [ -s outdated.json ]; then + echo "\`\`\`" >> ../../outdated_report.md + dart pub outdated >> ../../outdated_report.md 2>&1 || true + echo "\`\`\`" >> ../../outdated_report.md + else + echo "✅ All dependencies are up to date" >> ../../outdated_report.md + fi + else + echo "⚠️ Could not check dependencies" >> ../../outdated_report.md + fi + echo "" >> ../../outdated_report.md + cd ../.. + done + + - name: Run Dart dependency audit + id: audit + continue-on-error: true + run: | + echo "## Security Audit Report" > audit_report.md + echo "" >> audit_report.md + + # Check each package for security vulnerabilities + for package in packages/*/pubspec.yaml; do + package_dir=$(dirname "$package") + package_name=$(basename "$package_dir") + + echo "### Package: $package_name" >> audit_report.md + echo "" >> audit_report.md + + cd "$package_dir" + # Note: Using `dart pub get` with vulnerability checking + # Dart SDK has built-in vulnerability database + if dart pub get 2>&1 | grep -i "vulnerabilit" > /dev/null; then + echo "⚠️ Potential vulnerabilities detected:" >> ../../audit_report.md + echo "\`\`\`" >> ../../audit_report.md + dart pub get 2>&1 | grep -A 5 -i "vulnerabilit" >> ../../audit_report.md || true + echo "\`\`\`" >> ../../audit_report.md + else + echo "✅ No known vulnerabilities detected" >> ../../audit_report.md + fi + echo "" >> ../../audit_report.md + cd ../.. + done + + - name: Generate workflow summary + if: always() + run: | + echo "## Dependency Vulnerability Scan Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Scan Date:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY + echo "**Repository:** ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch:** ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + # Add outdated dependencies report + if [ -f outdated_report.md ]; then + cat outdated_report.md >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + # Add audit report + if [ -f audit_report.md ]; then + cat audit_report.md >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + fi + + # Overall status + if [ "${{ steps.audit.outcome }}" == "success" ] && [ "${{ steps.outdated.outcome }}" == "success" ]; then + echo "---" >> $GITHUB_STEP_SUMMARY + echo "✅ **Status:** Scan completed successfully" >> $GITHUB_STEP_SUMMARY + else + echo "---" >> $GITHUB_STEP_SUMMARY + echo "⚠️ **Status:** Scan completed with warnings" >> $GITHUB_STEP_SUMMARY + fi + + - name: Upload reports + if: always() + uses: actions/upload-artifact@v4 + with: + name: dependency-scan-reports-${{ github.run_number }} + path: | + outdated_report.md + audit_report.md + retention-days: 30 diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index ce0fdd52..38e03cc4 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -27,11 +27,13 @@ jobs: cache: true - name: Publish to pub.dev + id: publish uses: bluefireteam/melos-action@v3 with: publish: true - name: Create GitHub Release + id: create_release uses: softprops/action-gh-release@v1 with: tag_name: ${{ github.ref_name }} @@ -49,3 +51,52 @@ jobs: prerelease: false env: GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Generate workflow summary + if: always() + run: | + echo "## Package Release Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Package:** \`${{ github.ref_name }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [ "${{ steps.publish.outcome }}" == "success" ]; then + echo "✅ **Pub.dev Publishing:** Success" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Pub.dev Publishing:** Failed" >> $GITHUB_STEP_SUMMARY + fi + + if [ "${{ steps.create_release.outcome }}" == "success" ]; then + echo "✅ **GitHub Release:** Success" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **GitHub Release:** Failed" >> $GITHUB_STEP_SUMMARY + fi + + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.publish.outcome }}" == "success" ] && [ "${{ steps.create_release.outcome }}" == "success" ]; then + echo "🎉 Package published successfully!" >> $GITHUB_STEP_SUMMARY + else + echo "⚠️ Some steps failed. Please review the logs above." >> $GITHUB_STEP_SUMMARY + fi + + notify-success: + name: Notify Slack on Success + needs: publish-packages + if: ${{ needs.publish-packages.result == 'success' }} + uses: ./.github/workflows/slack-notify.yml + secrets: inherit + with: + title: 'Package Release' + status: 'success' + version: ${{ github.ref_name }} + + notify-failure: + name: Notify Slack on Failure + needs: publish-packages + if: ${{ always() && needs.publish-packages.result == 'failure' }} + uses: ./.github/workflows/slack-notify.yml + secrets: inherit + with: + title: 'Package Release' + status: 'failure' + version: ${{ github.ref_name }} diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index 10791689..2fc06e55 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -39,9 +39,46 @@ jobs: uses: bluefireteam/melos-action@v3 with: tag: true - - run: | - melos exec -c 1 --no-published --no-private --order-dependents -- \ - gh workflow run release-publish.yml \ - --ref \$MELOS_PACKAGE_NAME-v\$MELOS_PACKAGE_VERSION + + - name: Trigger package release workflows + id: trigger_releases + run: .github/scripts/trigger-package-releases.sh env: - GH_TOKEN: ${{ steps.app-token.outputs.token }} \ No newline at end of file + GH_TOKEN: ${{ steps.app-token.outputs.token }} + + - name: Generate workflow summary + if: always() + run: | + echo "## Release Tag Workflow Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + if [ "${{ steps.trigger_releases.outcome }}" == "success" ]; then + echo "✅ **Status:** All package release workflows triggered successfully" >> $GITHUB_STEP_SUMMARY + else + echo "❌ **Status:** Failed to trigger some package release workflows" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Error Details:**" >> $GITHUB_STEP_SUMMARY + echo "Please check the logs for the 'Trigger package release workflows' step for more information." >> $GITHUB_STEP_SUMMARY + fi + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Commit:** ${{ github.event.head_commit.message }}" >> $GITHUB_STEP_SUMMARY + echo "**Ref:** ${{ github.ref }}" >> $GITHUB_STEP_SUMMARY + + notify-success: + name: Notify Slack on Success + needs: create-tags + if: ${{ needs.create-tags.result == 'success' }} + uses: ./.github/workflows/slack-notify.yml + secrets: inherit + with: + title: 'Release Tags Created' + status: 'success' + + notify-failure: + name: Notify Slack on Failure + needs: create-tags + if: ${{ always() && needs.create-tags.result == 'failure' }} + uses: ./.github/workflows/slack-notify.yml + secrets: inherit + with: + title: 'Release Tags Creation' + status: 'failure' \ No newline at end of file diff --git a/.github/workflows/slack-notify.yml b/.github/workflows/slack-notify.yml new file mode 100644 index 00000000..6e657a39 --- /dev/null +++ b/.github/workflows/slack-notify.yml @@ -0,0 +1,130 @@ +name: Slack Notification (Reusable) + +on: + workflow_call: + inputs: + title: + required: true + type: string + description: 'Notification title (e.g., "Package Release", "Release Tags Created")' + status: + required: false + type: string + default: 'info' + description: 'Notification status: success, failure, or info' + version: + required: false + type: string + description: 'Version string to display (e.g., "supabase-v2.0.0")' + package: + required: false + type: string + description: 'Package name (e.g., "gotrue", "postgrest")' + +permissions: + contents: read + +jobs: + notify: + name: Send Slack Notification + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: Send Slack notification + run: | + # Determine status emoji and color + if [ "${{ inputs.status }}" == "success" ]; then + STATUS_EMOJI="✅" + COLOR="#36a64f" + elif [ "${{ inputs.status }}" == "failure" ]; then + STATUS_EMOJI="❌" + COLOR="#ff0000" + else + STATUS_EMOJI="ℹ️" + COLOR="#3498db" + fi + + # Build version text + VERSION_TEXT="" + if [ -n "${{ inputs.version }}" ]; then + VERSION_TEXT="\n*Version:* \`${{ inputs.version }}\`" + fi + + # Build package text + PACKAGE_TEXT="" + if [ -n "${{ inputs.package }}" ]; then + PACKAGE_TEXT="\n*Package:* \`${{ inputs.package }}\`" + fi + + # Build Slack message payload + PAYLOAD=$(cat <" + }, + { + "type": "mrkdwn", + "text": "*Workflow:*\n${{ github.workflow }}" + }, + { + "type": "mrkdwn", + "text": "*Branch:*\n\`${{ github.ref_name }}\`" + }, + { + "type": "mrkdwn", + "text": "*Triggered by:*\n${{ github.actor }}" + } + ] + }, + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "*Commit:*\n - ${{ github.event.head_commit.message }}${VERSION_TEXT}${PACKAGE_TEXT}" + } + }, + { + "type": "actions", + "elements": [ + { + "type": "button", + "text": { + "type": "plain_text", + "text": "View Workflow Run" + }, + "url": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + }, + { + "type": "button", + "text": { + "type": "plain_text", + "text": "View Commit" + }, + "url": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}" + } + ] + } + ] + } + EOF + ) + + # Send to Slack + curl -X POST "${{ secrets.SLACK_CLIENT_LIBS_WEBHOOK }}" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" + + echo "Slack notification sent successfully" diff --git a/README.md b/README.md index 6da5f54d..97952f4a 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ # `Supabase Flutter` +[![Dependency Scan](https://github.com/supabase/supabase-flutter/actions/workflows/dependency-scan.yml/badge.svg)](https://github.com/supabase/supabase-flutter/actions/workflows/dependency-scan.yml) + Flutter Client library for [Supabase](https://supabase.com/). - Documentation: https://supabase.com/docs/reference/dart/introduction