Skip to content

make lint

make lint #4

Workflow file for this run

name: Docs Unified Workflow
on:
workflow_call:
inputs:
preset:
description: 'Predefined configuration preset (pr, post-merge, weekly, ci)'
required: false
type: string
default: ''
lint-markdown:
description: 'Whether to lint markdown files (overrides preset)'
required: false
type: boolean
default: false
check-format:
description: 'Whether to check table formatting (overrides preset)'
required: false
type: boolean
default: false
check-links:
description: 'Whether to check links (overrides preset)'
required: false
type: boolean
default: false
check-cross-references:
description: 'Whether to check cross-references (overrides preset)'
required: false
type: boolean
default: false
lint-vale:
description: 'Whether to run Vale style checks (overrides preset)'
required: false
type: boolean
default: false
generate-preview:
description: 'Whether to generate preview links (overrides preset)'
required: false
type: boolean
default: false
post-comment:
description: 'Whether to post a PR comment (overrides preset)'
required: false
type: boolean
default: false
create-issues:
description: 'Whether to create GitHub issues (overrides preset)'
required: false
type: boolean
default: false
fail-on-error:
description: 'Whether to fail the workflow on errors (overrides preset)'
required: false
type: boolean
default: false
pr-number:
description: 'PR number for commenting (if passed from calling workflow)'
required: false
type: string
default: ''
notification-channels:
description: 'Comma-separated list of notification channels (github-issue, slack)'
required: false
type: string
default: ''
issue-labels:
description: 'Labels to apply to created issues (comma-separated)'
required: false
type: string
default: 'documentation,bug'
jobs:
docs-validation:
name: Documentation Validation
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write # needed for commenting on PRs
steps:
- name: Harden Runner
uses: step-security/harden-runner@c6295a65d1254861815972266d5933fd6e532bdf # v2.11.1
with:
egress-policy: audit
# Setup: Checkout code with full history for proper comparisons
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
fetch-depth: 0 # Fetch all history for proper file comparison
# Record start time for validation
- name: Record start time
id: start-time
shell: bash
run: |
echo "timestamp=$(date +%s)" >> $GITHUB_OUTPUT
# Apply configuration presets
- name: Apply configuration preset
id: config
shell: bash
run: |
echo "::group::Applying configuration settings"
# Default values (will be overridden by presets or specific inputs)
LINT_MARKDOWN="false"
CHECK_FORMAT="false"
CHECK_LINKS="false"
CHECK_XREFS="false"
LINT_VALE="false"
GEN_PREVIEW="false"
POST_COMMENT="false"
CREATE_ISSUES="false"
FAIL_ON_ERROR="false"
# Apply presets if specified
if [ -n "${{ inputs.preset }}" ]; then
case "${{ inputs.preset }}" in
"pr")
# PR preset: Comprehensive validation with preview for PRs
LINT_MARKDOWN="true" # Ensure markdown format is correct
CHECK_FORMAT="true" # Check table formatting
CHECK_LINKS="true" # Verify all links work
CHECK_XREFS="true" # Check document cross-references
LINT_VALE="true" # Run style checks
GEN_PREVIEW="true" # Generate preview links
POST_COMMENT="true" # Post comment with results
CREATE_ISSUES="false" # No auto-issue creation for PRs
FAIL_ON_ERROR="false" # Don't fail workflow to allow previews with warnings
echo "::notice::Applied PR preset configuration"
;;
"post-merge")
# Post-merge preset: Lightweight check focused on link integrity
LINT_MARKDOWN="false"
CHECK_FORMAT="false"
CHECK_LINKS="true" # Only check links after merge to main
CHECK_XREFS="false" # Cross-references should be checked pre-merge
LINT_VALE="false" # Style should be checked pre-merge
GEN_PREVIEW="false"
POST_COMMENT="false"
CREATE_ISSUES="false"
FAIL_ON_ERROR="false"
echo "::notice::Applied post-merge preset configuration"
;;
"weekly")
# Weekly check preset: Comprehensive validation with notifications (no style checks)
LINT_MARKDOWN="true"
CHECK_FORMAT="true"
CHECK_LINKS="true"
CHECK_XREFS="true"
LINT_VALE="false" # Skip Vale style checking in weekly checks
GEN_PREVIEW="false"
POST_COMMENT="false"
CREATE_ISSUES="false" # Issue creation feature is planned but not implemented yet
FAIL_ON_ERROR="true"
echo "::notice::Applied weekly check preset configuration"
;;
"ci")
# CI preset: Fast checks for CI pipelines
LINT_MARKDOWN="true"
CHECK_FORMAT="true"
CHECK_LINKS="false"
CHECK_XREFS="false"
LINT_VALE="false"
GEN_PREVIEW="false"
POST_COMMENT="false"
CREATE_ISSUES="false"
FAIL_ON_ERROR="true"
echo "::notice::Applied CI preset configuration"
;;
*)
echo "::warning::Unknown preset '${{ inputs.preset }}', using default values"
;;
esac
fi
# Apply explicit overrides if provided
if [ "${{ inputs.lint-markdown }}" == "true" ]; then
LINT_MARKDOWN="true"
elif [ "${{ inputs.lint-markdown }}" == "false" ]; then
LINT_MARKDOWN="false"
fi
if [ "${{ inputs.check-format }}" == "true" ]; then
CHECK_FORMAT="true"
elif [ "${{ inputs.check-format }}" == "false" ]; then
CHECK_FORMAT="false"
fi
if [ "${{ inputs.check-links }}" == "true" ]; then
CHECK_LINKS="true"
elif [ "${{ inputs.check-links }}" == "false" ]; then
CHECK_LINKS="false"
fi
if [ "${{ inputs.check-cross-references }}" == "true" ]; then
CHECK_XREFS="true"
elif [ "${{ inputs.check-cross-references }}" == "false" ]; then
CHECK_XREFS="false"
fi
if [ "${{ inputs.lint-vale }}" == "true" ]; then
LINT_VALE="true"
elif [ "${{ inputs.lint-vale }}" == "false" ]; then
LINT_VALE="false"
fi
if [ "${{ inputs.generate-preview }}" == "true" ]; then
GEN_PREVIEW="true"
elif [ "${{ inputs.generate-preview }}" == "false" ]; then
GEN_PREVIEW="false"
fi
if [ "${{ inputs.post-comment }}" == "true" ]; then
POST_COMMENT="true"
elif [ "${{ inputs.post-comment }}" == "false" ]; then
POST_COMMENT="false"
fi
if [ "${{ inputs.create-issues }}" == "true" ]; then
CREATE_ISSUES="true"
elif [ "${{ inputs.create-issues }}" == "false" ]; then
CREATE_ISSUES="false"
fi
if [ "${{ inputs.fail-on-error }}" == "true" ]; then
FAIL_ON_ERROR="true"
elif [ "${{ inputs.fail-on-error }}" == "false" ]; then
FAIL_ON_ERROR="false"
fi
# Store configuration as outputs
echo "lint_markdown=$LINT_MARKDOWN" >> $GITHUB_OUTPUT
echo "check_format=$CHECK_FORMAT" >> $GITHUB_OUTPUT
echo "check_links=$CHECK_LINKS" >> $GITHUB_OUTPUT
echo "check_xrefs=$CHECK_XREFS" >> $GITHUB_OUTPUT
echo "lint_vale=$LINT_VALE" >> $GITHUB_OUTPUT
echo "gen_preview=$GEN_PREVIEW" >> $GITHUB_OUTPUT
echo "post_comment=$POST_COMMENT" >> $GITHUB_OUTPUT
echo "create_issues=$CREATE_ISSUES" >> $GITHUB_OUTPUT
echo "fail_on_error=$FAIL_ON_ERROR" >> $GITHUB_OUTPUT
echo "::endgroup::"
# Extract context information for PR/branch
- name: Extract context information
id: context-info
shell: bash
run: |
echo "::group::Extracting context information"
# Extract PR number from inputs or context
if [ -n "${{ inputs.pr-number }}" ]; then
PR_NUMBER="${{ inputs.pr-number }}"
echo "::notice::Using PR number from action input: #${PR_NUMBER}"
elif [ "${{ github.event_name }}" == "pull_request" ]; then
PR_NUMBER="${{ github.event.pull_request.number }}"
echo "::notice::Using PR number from event context: #${PR_NUMBER}"
else
echo "::notice::No PR number available. Features requiring PR number will be disabled."
PR_NUMBER=""
fi
# Extract branch information (used for preview URLs)
if [ "${{ github.event_name }}" == "pull_request" ]; then
BRANCH_NAME="${{ github.head_ref }}"
else
BRANCH_NAME="${{ github.ref_name }}"
fi
# Sanitize branch name for URLs
SANITIZED_BRANCH="${BRANCH_NAME//\//-}"
# Generate preview URL
PREVIEW_URL="https://coder.com/docs/@$SANITIZED_BRANCH"
# Store variables for later steps
echo "pr_number=$PR_NUMBER" >> $GITHUB_OUTPUT
echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
echo "sanitized_branch=$SANITIZED_BRANCH" >> $GITHUB_OUTPUT
echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT
echo "::endgroup::"
# Post initial comment with preview links
- name: Post initial preview comment
if: inputs.post-comment == 'true' && inputs.generate-preview == 'true' && (inputs.pr-number != '' || github.event.pull_request)
uses: marocchino/sticky-pull-request-comment@v2.9.2
with:
header: docs-preview-comment
number: ${{ inputs.pr-number || github.event.pull_request.number }}
message: |
# 📚 Documentation Preview ⏳
## 🖥️ [View Documentation Preview](https://coder.com/docs/@${{ steps.context-info.outputs.sanitized_branch }})
> ℹ️ **Validation in progress**: Preview links are available now! This comment will update with validation results when complete.
### Quick Links
- [Main Docs](https://coder.com/docs/@${{ steps.context-info.outputs.sanitized_branch }})
- [Installation Guide](https://coder.com/docs/@${{ steps.context-info.outputs.sanitized_branch }}/install)
- [Quickstart](https://coder.com/docs/@${{ steps.context-info.outputs.sanitized_branch }}/tutorials/quickstart)
<sub>⏳ Validating documentation...</sub>
# Get changed files
- name: Get changed files
id: changed-files
uses: tj-actions/changed-files@v41
with:
files: |
docs/**/*.md
**/*.md
# Run MegaLinter (documentation flavor)
- name: MegaLinter Documentation
id: megalinter
uses: oxsecurity/megalinter/flavors/documentation@v7
if: steps.changed-files.outputs.all_changed_files != ''
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Only run specific linters based on configuration
ENABLE_LINTERS: ${{ steps.config.outputs.lint_markdown == 'true' && 'MARKDOWN_MARKDOWNLINT,' || '' }}${{ steps.config.outputs.lint_vale == 'true' && 'MARKDOWN_VALE,' || '' }}${{ steps.config.outputs.check_links == 'true' && 'MARKDOWN_MARKDOWN_LINK_CHECK,' || '' }}
# Directories to scan
MARKDOWN_VALE_FILE_EXTENSIONS: ".md"
MARKDOWN_VALE_FILTER_REGEX_INCLUDE: "(\\.md)$"
# Vale configuration
MARKDOWN_VALE_CONFIG_FILE: .github/docs/vale/.vale.ini
# Markdownlint configuration
MARKDOWN_MARKDOWNLINT_CONFIG_FILE: .github/docs/config/.markdownlint.yml
# Link checking
MARKDOWN_MARKDOWN_LINK_CHECK_CONFIG_FILE: .github/docs/config/markdown-link-check.json
MARKDOWN_MARKDOWN_LINK_CHECK_ARGUMENTS: "--quiet"
# Only check changed files
MEGALINTER_FILES_TO_LINT: ${{ steps.changed-files.outputs.all_changed_files }}
MEGALINTER_ONLY_UPDATED_FILES: true
# Report settings
DISABLE_ERRORS: ${{ steps.config.outputs.fail_on_error != 'true' }}
FILEIO_REPORTER: false
GITHUB_STATUS_REPORTER: false
GITHUB_COMMENT_REPORTER: false
TEXT_REPORTER: true
SARIF_REPORTER: true
JSON_REPORTER: true
# Custom cross-reference check for documentation changes
- name: Check cross-references
id: check-cross-refs
if: steps.config.outputs.check_xrefs == 'true' && steps.changed-files.outputs.all_changed_files != ''
shell: bash
run: |
echo "::group::Checking cross-references"
DOCS_DIR="docs"
FOUND_ISSUES=0
BROKEN_REFS=0
# Check for broken references to deleted files
if [ -n "${{ steps.changed-files.outputs.deleted_files }}" ]; then
echo "Checking for references to deleted files..."
# For each deleted file, check if any remaining files reference it
for deleted_file in ${{ steps.changed-files.outputs.deleted_files }}; do
# Skip non-markdown files
if [[ "$deleted_file" != *.md ]]; then
continue
fi
# Extract filename for matching
deleted_name=$(basename "$deleted_file")
# Use grep to find references to this file
echo "Checking references to: $deleted_name"
REFS_TO_DELETED=$(grep -l -r --include="*.md" "\[$deleted_name\]" "$DOCS_DIR" 2>/dev/null || echo "")
if [ -n "$REFS_TO_DELETED" ]; then
echo "::warning::Found references to deleted file '$deleted_name' in these files:"
echo "$REFS_TO_DELETED" | sed 's/^/ - /'
BROKEN_REFS=$((BROKEN_REFS + 1))
FOUND_ISSUES=1
fi
done
fi
# Check for broken cross-references in changed files
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Checking for broken internal links in changed files..."
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
# Skip non-markdown files
if [[ "$file" != *.md ]]; then
continue
fi
# Extract all internal links with the [text](link) pattern (exclude http/https links)
LINKS=$(grep -o -E '\[.+?\]\(\s*[^(http|https|mailto|#)][^)]+\s*\)' "$file" 2>/dev/null || echo "")
if [ -n "$LINKS" ]; then
# For each link, check if the target exists
echo "$LINKS" | while read -r link_match; do
# Extract just the URL part from [text](url)
link_url=$(echo "$link_match" | sed -E 's/\[.+?\]\(\s*([^)]+)\s*\)/\1/')
# Handle relative links correctly
if [[ "$link_url" == /* ]]; then
# Absolute path from repo root
link_path="$link_url"
else
# Relative path, get directory of current file
file_dir=$(dirname "$file")
link_path="$file_dir/$link_url"
fi
# Add .md extension if missing and it's likely a markdown link
if [[ ! "$link_path" == *.* ]]; then
link_path="${link_path}.md"
fi
# Clean up the path (remove double slashes, etc.)
link_path=$(echo "$link_path" | sed 's@//@/@g' | sed 's@\./@/@g')
# Check if the link target exists
if [ ! -f "$link_path" ]; then
echo "::warning::Broken link in $file: $link_match -> $link_path (file not found)"
BROKEN_REFS=$((BROKEN_REFS + 1))
FOUND_ISSUES=1
fi
done
fi
done
fi
# Check for broken image references
if [ -n "${{ steps.changed-files.outputs.all_changed_files }}" ]; then
echo "Checking for broken image references..."
for file in ${{ steps.changed-files.outputs.all_changed_files }}; do
# Skip non-markdown files
if [[ "$file" != *.md ]]; then
continue
fi
# Extract all image references with the ![text](link) pattern (exclude http/https links)
IMAGES=$(grep -o -E '!\[.+?\]\(\s*[^(http|https)][^)]+\s*\)' "$file" 2>/dev/null || echo "")
if [ -n "$IMAGES" ]; then
# For each image, check if it exists
echo "$IMAGES" | while read -r img_match; do
# Extract just the URL part from ![text](url)
img_url=$(echo "$img_match" | sed -E 's/!\[.+?\]\(\s*([^)]+)\s*\)/\1/')
# Handle relative paths correctly
if [[ "$img_url" == /* ]]; then
# Absolute path from repo root
img_path="$img_url"
else
# Relative path, get directory of current file
file_dir=$(dirname "$file")
img_path="$file_dir/$img_url"
fi
# Clean up the path (remove double slashes, etc.)
img_path=$(echo "$img_path" | sed 's@//@/@g' | sed 's@\./@/@g')
# Check if the image exists
if [ ! -f "$img_path" ]; then
echo "::warning::Broken image in $file: $img_match -> $img_path (file not found)"
BROKEN_REFS=$((BROKEN_REFS + 1))
FOUND_ISSUES=1
fi
done
fi
done
fi
# Set status based on findings
if [ $FOUND_ISSUES -eq 0 ]; then
echo "status=success" >> $GITHUB_OUTPUT
echo "message=No broken cross-references found" >> $GITHUB_OUTPUT
else
echo "status=warning" >> $GITHUB_OUTPUT
echo "message=Found $BROKEN_REFS broken cross-references" >> $GITHUB_OUTPUT
fi
echo "::endgroup::"
# Calculate validation duration
- name: Calculate validation duration
id: validation-duration
if: inputs.post-comment == 'true' && (inputs.pr-number != '' || github.event.pull_request)
shell: bash
run: |
START_TIME=${{ steps.start-time.outputs.timestamp }}
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
# Format duration as minutes and seconds
MINS=$((DURATION / 60))
SECS=$((DURATION % 60))
if [ $MINS -gt 0 ]; then
DURATION_TEXT="${MINS}m ${SECS}s"
else
DURATION_TEXT="${SECS}s"
fi
echo "duration=$DURATION_TEXT" >> $GITHUB_OUTPUT
# Prepare validation results
- name: Prepare validation results
id: validation-results
if: always() && steps.changed-files.outputs.all_changed_files != ''
shell: bash
run: |
echo "::group::Preparing validation results"
# Initialize results
VALIDATION_COUNT=0
PASSING_COUNT=0
SUCCESS_PERCENTAGE=0
OVERALL_SUCCESS="true"
# Function to process a validation result
process_validation() {
local name="$1"
local status="$2"
local message="$3"
VALIDATION_COUNT=$((VALIDATION_COUNT + 1))
if [ "$status" == "success" ]; then
PASSING_COUNT=$((PASSING_COUNT + 1))
echo "✅ $name: $message"
elif [ "$status" == "skipped" ]; then
echo "⏭️ $name: $message"
else
OVERALL_SUCCESS="false"
echo "❌ $name: $message"
fi
}
# Check MegaLinter results
if [ "${{ steps.config.outputs.lint_markdown }}" == "true" ] || [ "${{ steps.config.outputs.lint_vale }}" == "true" ] || [ "${{ steps.config.outputs.check_links }}" == "true" ]; then
MEGALINTER_STATUS="${{ steps.megalinter.outcome }}"
if [ "$MEGALINTER_STATUS" == "success" ]; then
process_validation "MegaLinter" "success" "All linting checks passed"
elif [ "$MEGALINTER_STATUS" == "skipped" ]; then
process_validation "MegaLinter" "skipped" "MegaLinter was not run"
else
process_validation "MegaLinter" "error" "Some linting checks failed"
fi
fi
# Check cross-references results
if [ "${{ steps.config.outputs.check_xrefs }}" == "true" ]; then
XREFS_STATUS="${{ steps.check-cross-refs.outputs.status }}"
XREFS_MESSAGE="${{ steps.check-cross-refs.outputs.message }}"
if [ "$XREFS_STATUS" == "success" ]; then
process_validation "Cross-references" "success" "$XREFS_MESSAGE"
elif [ "$XREFS_STATUS" == "skipped" ]; then
process_validation "Cross-references" "skipped" "$XREFS_MESSAGE"
else
process_validation "Cross-references" "warning" "$XREFS_MESSAGE"
fi
fi
# Calculate success percentage
if [ $VALIDATION_COUNT -gt 0 ]; then
SUCCESS_PERCENTAGE=$((PASSING_COUNT * 100 / VALIDATION_COUNT))
else
SUCCESS_PERCENTAGE=100 # No validations ran
fi
# Create badge text
if [ "$OVERALL_SUCCESS" == "true" ]; then
BADGE="✅ All validation checks passed"
elif [ $SUCCESS_PERCENTAGE -ge 80 ]; then
BADGE="⚠️ Most validation checks passed"
else
BADGE="❌ Several validation issues were found"
fi
# Set outputs
echo "validation_count=$VALIDATION_COUNT" >> $GITHUB_OUTPUT
echo "passing_count=$PASSING_COUNT" >> $GITHUB_OUTPUT
echo "success_percentage=$SUCCESS_PERCENTAGE" >> $GITHUB_OUTPUT
echo "overall_success=$OVERALL_SUCCESS" >> $GITHUB_OUTPUT
echo "badge=$BADGE" >> $GITHUB_OUTPUT
echo "::endgroup::"
# Prepare comment for PR
- name: Prepare PR comment
id: prepare-comment
if: inputs.post-comment == 'true' && (inputs.pr-number != '' || github.event.pull_request) && steps.changed-files.outputs.all_changed_files != ''
shell: bash
run: |
echo "::group::Preparing PR comment"
# Build the comment
SANITIZED_BRANCH="${{ steps.context-info.outputs.sanitized_branch }}"
PREVIEW_URL="https://coder.com/docs/@$SANITIZED_BRANCH"
# Create comment header
if [ "${{ steps.validation-results.outputs.overall_success }}" == "true" ]; then
HEADER="# 📚 Documentation Preview ✅"
STATUS_EMOJI="✅"
else
HEADER="# 📚 Documentation Preview ⚠️"
STATUS_EMOJI="⚠️"
fi
# Build the full comment
COMMENT="$HEADER
## 🖥️ [View Documentation Preview]($PREVIEW_URL)
> $STATUS_EMOJI **Validation Result**: ${{ steps.validation-results.outputs.badge }}

Check failure on line 619 in .github/workflows/docs-unified.yaml

View workflow run for this annotation

GitHub Actions / .github/workflows/docs-unified.yaml

Invalid workflow file

You have an error in your yaml syntax on line 619
### Quick Links
- [Main Docs]($PREVIEW_URL)
- [Installation Guide]($PREVIEW_URL/install)
- [Quickstart]($PREVIEW_URL/tutorials/quickstart)
### 📊 Validation Stats
- **Changed Files**: ${{ steps.changed-files.outputs.all_changed_files_count }} files checked
- **Validation Success**: ${{ steps.validation-results.outputs.success_percentage }}% (${{ steps.validation-results.outputs.passing_count }}/${{ steps.validation-results.outputs.validation_count }} checks passed)
- **Processing Time**: ${{ steps.validation-duration.outputs.duration }}
<sub>⏱️ Validation completed in ${{ steps.validation-duration.outputs.duration }} | [View Workflow Run](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})</sub>"
# Save the comment to output
echo "comment<<EOF" >> $GITHUB_OUTPUT
echo "$COMMENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
echo "::endgroup::"
# Update the PR comment with results
- name: Update PR comment with results
if: inputs.post-comment == 'true' && (inputs.pr-number != '' || github.event.pull_request) && steps.changed-files.outputs.all_changed_files != ''
uses: marocchino/sticky-pull-request-comment@v2.9.2
with:
header: docs-preview-comment
number: ${{ inputs.pr-number || github.event.pull_request.number }}
message: ${{ steps.prepare-comment.outputs.comment }}
recreate: true
# Fail the workflow if specified and there are errors
- name: Check for validation failure
if: steps.config.outputs.fail-on-error == 'true' && steps.validation-results.outputs.overall_success == 'false'
shell: bash
run: |
echo "::error::Documentation validation failed with ${{ steps.validation-results.outputs.badge }}"
exit 1