name: Pull Request Docs Draft on: pull_request: branches: - '**' paths: - 'docs/**' - '.github/workflows/deploy-docs-draft.yml' concurrency: group: ${{ github.workflow }}-${{ github.ref }} jobs: build-and-deploy: runs-on: ubuntu-latest if: "! github.event.pull_request.head.repo.fork" steps: - name: Checkout Repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: 20 cache: yarn cache-dependency-path: ./docs/yarn.lock - name: Validate Branch Names run: | # Check if branch names contain invalid characters. Only alphanumeric, _, -, ., and / are allowed. validate_branch_name() { local branch_name="$1" if [[ ! "$branch_name" =~ ^[a-zA-Z0-9/_\.-]+$ ]]; then echo "Error: Branch name contains invalid characters. Only alphanumeric, _, -, ., and / are allowed." exit 1 fi } validate_branch_name "${{ github.event.pull_request.head.ref }}" - name: Extract Branch Names id: extract_branch run: | # Extract and transform branch names extract_branch() { local input_branch="$1" # Check if input_branch starts with "refs/heads/" if [[ "$input_branch" == refs/heads/* ]]; then # Remove "refs/heads/" prefix safely using parameter expansion branch_name="${input_branch#refs/heads/}" echo "$branch_name" else echo "$input_branch" fi } # Transform branch names in form of `refs/heads/main` to `main` draft_branch=$(extract_branch "${{ github.event.pull_request.head.ref }}") # Replace / with - in the draft branch name to use as a directory name draft_directory=$(echo "$draft_branch" | tr / -) # Safe echo to $GITHUB_OUTPUT { echo "draft_branch=$draft_branch" echo "draft_directory=$draft_directory" } >> "$GITHUB_OUTPUT" - name: Set Draft URL id: draft_url if: success() run: | echo "url=${{ vars.DOCS_DRAFT_BASE_URL }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/index.html" >> $GITHUB_OUTPUT - name: Install dependencies run: cd docs && yarn install - name: Build website if: success() run: | set -o pipefail cd docs yarn build |& tee $GITHUB_WORKSPACE/build.log env: BASE_URL: /langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }} FORCE_COLOR: 0 # Disable color output # SEGMENT_PUBLIC_WRITE_KEY: ${{ vars.DOCS_DRAFT_SEGMENT_PUBLIC_WRITE_KEY }} - name: Check Build Result id: buildLogFail if: failure() run: | MULTILINE_LOG=$(cat $GITHUB_WORKSPACE/build.log) echo "BUILD_FAILURE<> $GITHUB_ENV echo $MULTILINE_LOG >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Hide Previous Build Comments if: ${{ github.event.pull_request.number && (success() || failure()) }} run: | set -e # Get all comments on the PR that match our build comments comments=$(gh api repos/${{ github.repository }}/issues/${{ github.event.pull_request.number }}/comments \ --jq '.[] | select(.body | test("Build failure! :x:|Build successful! :white_check_mark:")) | .node_id') # Minimize each matching comment using GraphQL API if [[ -n "$comments" ]]; then echo "Found previous build comments to hide" while IFS= read -r comment_id; do if [[ -n "$comment_id" ]]; then echo "Minimizing comment: $comment_id" gh api graphql \ --field id="$comment_id" \ --field classifier="OUTDATED" \ --raw-field query=' mutation($id: ID!, $classifier: ReportedContentClassifiers!) { minimizeComment(input: { subjectId: $id, classifier: $classifier }) { minimizedComment { isMinimized } } }' || echo "Failed to minimize comment $comment_id, continuing..." echo fi done <<< "$comments" else echo "No previous build comments found to hide" fi env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Assemble Build Success Comment if: success() run: | build_success_comment="Build successful! :white_check_mark:" build_success_comment+="\nDeploying docs draft." echo "BUILD_SUCCESS_COMMENT<> $GITHUB_ENV echo -e "$build_success_comment" >> $GITHUB_ENV echo "EOF" >> $GITHUB_ENV - name: Create Build Success Comment if: success() uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} body: "${{ env.BUILD_SUCCESS_COMMENT }}" reactions: rocket - name: Create Build Failure Comment if: failure() uses: peter-evans/create-or-update-comment@v4 with: issue-number: ${{ github.event.pull_request.number }} body: | Build failure! :x: > ${{ env.BUILD_FAILURE }} reactions: confused - name: Find Comment id: fc if: success() uses: peter-evans/find-comment@v3 with: issue-number: ${{ github.event.pull_request.number }} body-includes: Build successful! direction: last - name: Configure AWS CLI if: success() run: | aws configure set aws_access_key_id ${{ secrets.DOCS_AWS_ACCESS_KEY_ID }} aws configure set aws_secret_access_key ${{ secrets.DOCS_AWS_SECRET_ACCESS_KEY }} aws configure set region us-west-2 - name: Check for New Assets run: | set -o pipefail echo "Checking for new assets." |& tee -a $GITHUB_WORKSPACE/deploy.log echo "aws s3 sync docs/build/assets/ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/ --size-only --dryrun --no-progress" | tee -a $GITHUB_WORKSPACE/deploy.log aws s3 sync docs/build/assets/ "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/assets/" --size-only --dryrun --no-progress | tee $GITHUB_WORKSPACE/assets.log - name: Determine Standard or Full Publish id: check_full_publish run: | # Determine if a full publish is required because of new assets. if grep -qE '(upload:|delete:)' "$GITHUB_WORKSPACE/assets.log"; then echo "New assets. Perform full publish: true" | tee -a "$GITHUB_WORKSPACE/deploy.log" echo "perform_full_publish=true" >> "$GITHUB_OUTPUT" else echo "No new assets. Perform full publish: false" | tee -a "$GITHUB_WORKSPACE/deploy.log" echo "perform_full_publish=false" >> "$GITHUB_OUTPUT" fi - name: Deploy to S3 if: success() run: | set -o pipefail cd docs mkdir langflow-drafts mv build langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }} cd langflow-drafts # Records the repository that originally triggered the build so we can post back # comments upon clean up of a stale draft if it still has an open pull request. echo "${{ github.event.repository.full_name }}" > ${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository s3_params=( # Hide upload progress for a cleaner sync log --no-progress --delete --exclude "*" --include "${{ steps.extract_branch.outputs.draft_directory }}/*" ) if [[ "${{ steps.check_full_publish.outputs.perform_full_publish }}" == "false" ]]; then s3_params+=(--size-only) fi echo "Deploying draft to S3." |& tee -a $GITHUB_WORKSPACE/deploy.log echo "aws s3 sync . s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts ${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log aws s3 sync . "s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts" "${s3_params[@]}" |& tee -a $GITHUB_WORKSPACE/deploy.log # Update .github_source_repository file metadata to mark last modified time of the draft. # This will allow us to later determine if a draft is stale and needs to be cleaned up. echo "Marking last modified time of the draft." |& tee -a $GITHUB_WORKSPACE/deploy.log echo "aws s3 cp --metadata '{\"touched\": \"now\"}' \ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository" \ |& tee -a $GITHUB_WORKSPACE/deploy.log aws s3 cp --metadata '{ "touched": "now" }' \ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \ s3://${{ vars.DOCS_DRAFT_S3_BUCKET_NAME }}/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/.github_source_repository \ |& tee -a $GITHUB_WORKSPACE/deploy.log - name: Invalidate CloudFront Cache if: success() run: | invalidation_batch="{ \"Paths\": { \"Quantity\": 1, \"Items\": [\"/langflow-drafts/${{ steps.extract_branch.outputs.draft_directory }}/*\"] }, \"CallerReference\": \"langflow-docs-draft-files-$(date +%s)\" }" echo $invalidation_batch | jq . |& tee -a "$GITHUB_WORKSPACE/deploy.log" echo "Creating invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log" invalidation_id=$(aws cloudfront create-invalidation --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --invalidation-batch "$invalidation_batch" --query 'Invalidation.Id' --output text |& tee -a "$GITHUB_WORKSPACE/deploy.log") echo "Awaiting invalidation." |& tee -a "$GITHUB_WORKSPACE/deploy.log" aws cloudfront wait invalidation-completed --distribution-id "${{ vars.DOCS_DRAFT_CLOUD_FRONT_DISTRIBUTION_ID }}" --id "$invalidation_id" |& tee -a "$GITHUB_WORKSPACE/deploy.log" echo "Invalidation complete." |& tee -a "$GITHUB_WORKSPACE/deploy.log" - name: Update Comment if: ${{ steps.fc.outputs.comment-id != '' }} uses: peter-evans/create-or-update-comment@v4 with: comment-id: ${{ steps.fc.outputs.comment-id }} body: | Deploy successful! [View draft](${{ steps.draft_url.outputs.url }}) reactions: hooray - name: Upload Deploy Log uses: actions/upload-artifact@v4 if: always() with: name: deploy.log path: ${{ github.workspace }}/deploy.log