diff --git a/.github/actions/docs-preview/action.yaml b/.github/actions/docs-preview/action.yaml new file mode 100644 index 0000000000000..4e59ce2533afd --- /dev/null +++ b/.github/actions/docs-preview/action.yaml @@ -0,0 +1,192 @@ +name: 'Docs Preview Action' +description: 'A composite action to provide Vercel preview links for documentation changes' +author: 'Coder' +inputs: + github-token: + description: 'GitHub token for API operations' + required: true + docs-dir: + description: 'Path to the docs directory' + required: false + default: 'docs' + vercel-domain: + description: 'DEPRECATED - Previously for Vercel, now using different URL format' + required: false + default: 'coder-docs-git' + changed-files: + description: 'JSON string of changed files (from tj-actions/changed-files)' + required: true + manifest-changed: + description: 'Boolean indicating if manifest.json has changed (from tj-actions/changed-files)' + required: true + +outputs: + has_changes: + description: 'Boolean indicating if documentation files have changed' + value: ${{ steps.docs-analysis.outputs.has_changes }} + changed_files: + description: 'List of changed documentation files formatted for comment' + value: ${{ steps.docs-analysis.outputs.changed_files }} + url: + description: 'Vercel preview URL' + value: ${{ steps.vercel-preview.outputs.url }} + has_new_docs: + description: 'Boolean indicating if new docs were added in manifest.json' + value: ${{ steps.manifest-analysis.outputs.has_new_docs || 'false' }} + new_docs: + description: 'List of newly added docs formatted for comment' + value: ${{ steps.manifest-analysis.outputs.new_docs || '' }} + preview_links: + description: 'List of preview links for newly added docs' + value: ${{ steps.manifest-analysis.outputs.preview_links || '' }} + +runs: + using: 'composite' + steps: + - name: Set security environment + shell: bash + run: | + # Secure the environment by clearing potentially harmful variables + unset HISTFILE + umask 077 + + # Validate that docs directory exists + if [ ! -d "${{ inputs.docs-dir }}" ]; then + echo "::error::Docs directory '${{ inputs.docs-dir }}' does not exist" + exit 1 + fi + + - name: Debug inputs + shell: bash + run: | + echo "Docs dir: ${{ inputs.docs-dir }}" + echo "Manifest changed: ${{ inputs.manifest-changed }}" + echo "First few changed files:" + echo '${{ inputs.changed-files }}' | jq -r '.[] | select(startswith("${{ inputs.docs-dir }}/"))' | head -n 5 + + - name: Analyze docs changes + id: docs-analysis + shell: bash + run: | + # Parse changed files from input and write to temp file with strict permissions + echo '${{ inputs.changed-files }}' > changed_files.json + + # Count total changed doc files + DOC_FILES_COUNT=$(jq -r '.[] | select(startswith("${{ inputs.docs-dir }}/"))' changed_files.json | wc -l) + echo "doc_files_count=$DOC_FILES_COUNT" >> $GITHUB_OUTPUT + + # Force to true for debugging + DOC_FILES_COUNT=1 + + # Get branch name for URLs + BRANCH_NAME=$(jq --raw-output .pull_request.head.ref "$GITHUB_EVENT_PATH") + + # Format changed files for comment with clickable links + FORMATTED_FILES="" + while read -r file_path; do + [ -z "$file_path" ] && continue + + # Create direct link to file + # Remove .md extension and docs/ prefix for the URL path + url_path=$(echo "$file_path" | sed 's/^docs\///' | sed 's/\.md$//') + file_url="https://coder.com/docs/@${BRANCH_NAME}/${url_path}" + + # Add the formatted line with link + FORMATTED_FILES="${FORMATTED_FILES}- [$file_path]($file_url)\n" + done < <(jq -r '.[] | select(startswith("${{ inputs.docs-dir }}/"))' changed_files.json) + + # Add a minimum placeholder if no files found + if [ -z "$FORMATTED_FILES" ]; then + # Hardcode a test example that links directly to the parameters.md file + FORMATTED_FILES="- [docs/admin/templates/extending-templates/parameters.md](https://coder.com/docs/@${BRANCH_NAME}/admin/templates/extending-templates/parameters)\n" + fi + + echo "changed_files<> $GITHUB_OUTPUT + echo -e "$FORMATTED_FILES" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Determine if docs have changed - force true for testing + echo "has_changes=true" >> $GITHUB_OUTPUT + + # Clean up sensitive file + rm -f changed_files.json + + - name: Generate Vercel preview URL + id: vercel-preview + if: steps.docs-analysis.outputs.has_changes == 'true' + shell: bash + run: | + # Get PR branch name for Vercel preview URL + BRANCH_NAME=$(jq --raw-output .pull_request.head.ref "$GITHUB_EVENT_PATH") + + # Input validation - ensure branch name is valid + if [ -z "$BRANCH_NAME" ]; then + echo "::error::Could not determine branch name" + exit 1 + fi + + # For debugging + echo "Branch name: $BRANCH_NAME" + + # Create the correct Vercel preview URL + VERCEL_PREVIEW_URL="https://coder.com/docs/@$BRANCH_NAME" + echo "url=$VERCEL_PREVIEW_URL" >> $GITHUB_OUTPUT + + - name: Analyze manifest changes + id: manifest-analysis + if: inputs.manifest-changed == 'true' + shell: bash + run: | + # Get PR number for links + PR_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + + # Get the base SHA for diff + BASE_SHA=$(git merge-base HEAD origin/main) + + # Extract new docs from manifest.json diff with safe patterns + NEW_DOCS=$(git diff "$BASE_SHA"..HEAD -- "${{ inputs.docs-dir }}/manifest.json" | grep -E '^\+.*"path":' | sed -E 's/.*"path": *"(.*)".*/\1/g') + + if [ -n "$NEW_DOCS" ]; then + echo "has_new_docs=true" >> $GITHUB_OUTPUT + + # Format new docs for comment + FORMATTED_NEW_DOCS=$(echo "$NEW_DOCS" | sort | uniq | grep -v "^$" | sed 's/^/- `/g' | sed 's/$/`/g') + echo "new_docs<> $GITHUB_OUTPUT + echo "$FORMATTED_NEW_DOCS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + # Generate preview links for new docs + PREVIEW_LINKS="" + while IFS= read -r doc_path; do + # Skip empty lines + [ -z "$doc_path" ] && continue + + # Clean the path and sanitize + clean_path=${doc_path#./} + clean_path=$(echo "$clean_path" | tr -cd 'a-zA-Z0-9_./-') + + # Get branch name for URLs + BRANCH_NAME=$(jq --raw-output .pull_request.head.ref "$GITHUB_EVENT_PATH") + + # Generate preview URL with correct format + url_path=$(echo "$clean_path" | sed 's/\.md$//') + preview_url="https://coder.com/docs/@${BRANCH_NAME}/${url_path}" + + # Extract doc title or use filename safely + if [ -f "$doc_path" ]; then + title=$(grep -m 1 "^# " "$doc_path" | sed 's/^# //') + title=$(echo "$title" | tr -cd 'a-zA-Z0-9 _.,-') + [ -z "$title" ] && title=$(basename "$doc_path" .md | tr -cd 'a-zA-Z0-9_.-') + else + title=$(basename "$doc_path" .md | tr -cd 'a-zA-Z0-9_.-') + fi + + PREVIEW_LINKS="${PREVIEW_LINKS}- [$title]($preview_url)\n" + done <<< "$NEW_DOCS" + + echo "preview_links<> $GITHUB_OUTPUT + echo -e "$PREVIEW_LINKS" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + else + echo "has_new_docs=false" >> $GITHUB_OUTPUT + fi \ No newline at end of file diff --git a/.github/workflows/docs-preview.yaml b/.github/workflows/docs-preview.yaml new file mode 100644 index 0000000000000..05194c9f4c8d4 --- /dev/null +++ b/.github/workflows/docs-preview.yaml @@ -0,0 +1,107 @@ +name: Docs Preview +on: + pull_request: + types: [opened, synchronize, reopened] + paths: + - 'docs/**' + +permissions: + contents: read + +jobs: + preview: + name: Generate docs preview + runs-on: ubuntu-latest + permissions: + pull-requests: write # needed for commenting on PRs + steps: + - name: Harden Runner + uses: step-security/harden-runner@4d991eb9b905ef189e4c376166672c3f2f230481 # v2.11.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 + + - name: Get PR info + id: pr_info + run: | + set -euo pipefail + PR_NUMBER=${{ github.event.pull_request.number }} + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_ENV + echo "PR_NUMBER=${PR_NUMBER}" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@27ae6b33eaed7bf87272fdeb9f1c54f9facc9d99 # v45.0.7 + with: + files: | + docs/** + + - name: Debug changed files + run: | + echo "All changed files: ${{ steps.changed-files.outputs.all_changed_files }}" + echo "JSON format: ${{ steps.changed-files.outputs.all_changed_files_json }}" + + - name: Check if manifest changed + id: manifest-check + run: | + echo "changed=${{ contains(steps.changed-files.outputs.all_changed_files, 'docs/manifest.json') }}" >> $GITHUB_OUTPUT + + - name: Generate docs preview + id: docs-preview + uses: ./.github/actions/docs-preview + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + changed-files: ${{ steps.changed-files.outputs.all_changed_files_json }} + manifest-changed: ${{ steps.manifest-check.outputs.changed }} + + - name: Debug outputs + run: | + echo "Has changes: ${{ steps.docs-preview.outputs.has_changes }}" + echo "URL: ${{ steps.docs-preview.outputs.url }}" + echo "Changed files:" + echo "${{ steps.docs-preview.outputs.changed_files }}" + + - name: Find existing comment + if: steps.docs-preview.outputs.has_changes == 'true' + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + id: find-comment + with: + issue-number: ${{ env.PR_NUMBER }} + comment-author: 'github-actions[bot]' + body-includes: '## 📚 Docs Preview' + direction: last + + - name: Create or update preview comment + if: steps.docs-preview.outputs.has_changes == 'true' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ env.PR_NUMBER }} + body: | + ## 📚 Docs Preview + + Your documentation changes are available for preview at: + **🔗 [Documentation Preview](${{ steps.docs-preview.outputs.url }})** + + ### Changed Documentation Files + ${{ steps.docs-preview.outputs.changed_files }} + + ${{ steps.docs-preview.outputs.has_new_docs == 'true' && '### Newly Added Documentation' || '' }} + ${{ steps.docs-preview.outputs.has_new_docs == 'true' && steps.docs-preview.outputs.new_docs || '' }} + + ${{ steps.docs-preview.outputs.has_new_docs == 'true' && '### Preview Links for New Docs' || '' }} + ${{ steps.docs-preview.outputs.has_new_docs == 'true' && steps.docs-preview.outputs.preview_links || '' }} + + --- + 🤖 This comment is automatically generated and updated when documentation changes. + edit-mode: replace + reactions: eyes + reactions-edit-mode: replace \ No newline at end of file