diff --git a/.github/docs/README.md b/.github/docs/README.md new file mode 100644 index 0000000000000..c4f60ade4f834 --- /dev/null +++ b/.github/docs/README.md @@ -0,0 +1,273 @@ +# Coder Documentation Workflow + +This directory contains GitHub Actions, configurations, and workflows for Coder's unified documentation validation system. + +## Developer Quick Start Guide + +```bash +# Check only changed documentation files (default behavior) +make lint/docs + +# Check ALL documentation files +make lint/docs --all + +# Format markdown tables and fix common issues +make fmt/markdown + +# Run Vale style check (optional, requires Vale installation) +make lint/docs-style +``` + +The validation system will automatically detect and check only files that have changed in your working directory. This ensures fast feedback during development. + +## Directory Structure + +- `config`: Configuration files for markdown tools (markdownlint, markdown-link-check) +- `vale`: Configuration and style rules for Vale documentation linting +- `testing`: Test scripts and utilities for workflow validation + +## Quick Start + +For developers working with documentation, here are the most commonly used commands: + +```bash +# Run comprehensive documentation validation (markdown lint + link checking) +make lint/docs + +# Run only markdown linting +make lint/markdown + +# Run optional style checking with Vale (if installed) +make lint/docs-style + +# Fix formatting issues +make fmt/markdown # Formats tables and markdown styling +``` + +## Local vs CI Validation + +The validation that runs in CI is available locally through the same Makefile targets: + +| GitHub Action | Local Command | Description | +|---------------|--------------|-------------| +| Markdown linting | `make lint/markdown` | Checks markdown formatting | +| Link checking | `make lint/docs` | Verifies links aren't broken | +| Vale style checking | `make lint/docs-style` (optional) | Validates documentation style with Vale | +| Cross-reference validation | *Part of CI only* | Checks references between docs | + +### Optional Tool Installation + +While basic linting works out-of-the-box with node dependencies, additional tools can be installed for more advanced checks: + +```bash +# Install Lychee for link checking (recommended) +cargo install lychee + +# Install Vale for style checking (optional) +brew install vale + +# Node dependencies for markdown formatting (required) +pnpm install +``` + +# Coder Documentation Workflow System + +## Workflow Architecture + +The documentation workflow system uses MegaLinter and standardized GitHub Actions to provide a comprehensive validation pipeline: + +``` +┌─ Workflow Entry Points ───────────────────────────────────────────────────────┐ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌───────────┐ │ +│ │ PR Preview │ │ Post-Merge │ │ Weekly │ │ CI Check │ │ +│ │ Workflow │ │ Validation │ │ Checks │ │ Workflow │ │ +│ │ docs-preview.yml│ │ docs-ci.yml │ │weekly-docs.yml │ │docs-ci.yml│ │ +│ │ │ │ │ │ │ │ │ │ +│ │ • Runs on PR │ │ • Runs after │ │ • Runs weekly │ │ • Runs on │ │ +│ │ creation/update│ │ merges to main│ │ on schedule │ │ PR │ │ +│ │ • Generates │ │ • Checks links │ │ • Comprehensive │ │ • Basic │ │ +│ │ preview links │ │ only │ │ validation │ │ checks │ │ +│ │ • Validates docs│ │ • Falls back to │ │ • Planned: issues│ │ • Fast │ │ +│ │ • Posts comments│ │ original doc │ │ for problems │ │ feedback│ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ └─────┬─────┘ │ +│ │ │ │ │ │ +└───────────┼────────────────────┼────────────────────┼──────────────────┼───────┘ + │ │ │ │ + └──────────┬─────────┴──────────┬─────────┴──────────┬──────┘ + │ │ │ + ▼ ▼ ▼ +┌─ Unified Reusable Workflow ─────────────────────────────────────────────────────┐ +│ │ +│ docs-unified.yaml │ +│ ┌───────────────────────────────────────────────────────────────────────────┐ │ +│ │ Configuration System │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ PR Preset │ │Post Preset │ │Weekly Preset│ │ CI Preset │ │ │ +│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────────┐ │ +│ │ Validation Pipeline │ │ +│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ +│ │ │ MegaLinter │ │ Cross-reference│ │ Result Reporting│ │ │ +│ │ │ Documentation │ │ Validation │ │ and Comments │ │ │ +│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─ Local Development Integration ────────────────────────────────────────────────┐ +│ │ +│ Makefile targets that mirror workflow functionality: │ +│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ +│ │ make lint/docs│ │make fmt/markdown│ │make lint/markdown│ │make lint/docs-style│ │ +│ └───────────────┘ └───────────────┘ └───────────────┘ └───────────────┘ │ +│ │ +``` + +## Documentation Workflow Components + +### Entry Point Workflows + +The system provides four specialized entry points for different validation scenarios: + +1. **PR Preview (docs-preview.yaml)** + - Triggers on PR create/update when docs files change + - Performs comprehensive validation on documentation changes + - Generates preview links and posts PR comments with results + - Skips link checking for faster feedback + +2. **Post-Merge Validation (docs-link-check.yaml)** + - Runs after merges to main branch + - Lightweight check focused only on link integrity + - Ensures merged content maintains external link validity + +3. **Weekly Check (weekly-docs.yaml)** + - Scheduled run every Monday at 9 AM + - Comprehensive validation of documentation health + - Checks links, cross-references, markdown structure, and formatting + - Creates issues for persistent problems + +4. **CI Check (docs-ci.yaml)** + - Fast validation for continuous integration + - Focuses on formatting and structural issues + - Designed for rapid feedback + +### Unified Workflow & Presets + +All entry points use the central `docs-unified.yaml` workflow with different preset configurations: + +| Preset | Description | Main Validations | When Used | +|--------|-------------|------------------|-----------| +| `pr` | Complete validation for PRs | markdown, formatting, style, cross-references | PRs that modify docs (preview workflow) | +| `post-merge` | Lightweight check after merge | links | After merging to main (catches broken links) | +| `weekly` | Scheduled health check | markdown, formatting, links, cross-references | Weekly cron job (comprehensive check) | +| `ci` | Fast CI validation | markdown, formatting | PR checks (fast feedback) | + +## Key Tools and Integrations + +### MegaLinter Documentation Flavor + +The workflow leverages MegaLinter's documentation flavor to provide comprehensive validation: + +- **markdownlint**: Validates markdown syntax and formatting +- **Vale**: Checks documentation style and terminology +- **markdown-link-check**: Verifies links are valid and accessible + +Configuration files are stored in standardized locations: +- `.github/docs/config/.markdownlint.yml`: Markdown linting rules +- `.github/docs/vale/.vale.ini`: Vale style configuration +- `.github/docs/config/markdown-link-check.json`: Link checking settings + +### Changed Files Detection + +The workflow uses tj-actions/changed-files to efficiently detect changed files: + +```yaml +# Get changed files +- name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v41 + with: + files: | + docs/**/*.md + **/*.md +``` + +### Cross-Reference Validation + +Custom cross-reference validation checks for broken internal links: + +- References to deleted files +- Broken internal markdown links +- Missing image references + +## Vale Style Checking + +The workflow includes Vale style checking that: +- Only examines changed files to improve performance +- Validates documentation against Coder style guidelines +- Uses the errata-ai/vale-action GitHub Action +- Is configured in `.github/docs/vale/` with custom rules + +### Vale Style Rules + +The following style rules are configured: + +| Rule | Description | Severity | +|------|-------------|----------| +| `Coder.Headings` | Ensures proper heading capitalization | warning | +| `Coder.Terms` | Enforces consistent terminology | warning | +| `Coder.RepeatedWords` | Catches repeated words like "the the" | error | +| `Coder.SentenceLength` | Warns about overly long sentences | suggestion | +| `GitLab.*` | Various rules from GitLab style guide | varies | + +To suppress a Vale rule for a specific line: + +```markdown + +This is a very long sentence that would normally trigger the sentence length rule but has been explicitly exempted for a good reason such as a technical requirement or quotation. + +``` + +## Workflow Configuration Options + +Each workflow entry point can be customized with these key parameters: + +| Parameter | Description | Default | +|-----------|-------------|---------| +| `preset` | Predefined configuration bundle | (required) | +| `lint-markdown` | Run markdown linting | (from preset) | +| `check-format` | Validate table formatting | (from preset) | +| `check-links` | Verify link integrity | (from preset) | +| `check-cross-references` | Check documentation cross-references | (from preset) | +| `lint-vale` | Run Vale style validation | (from preset) | +| `generate-preview` | Create preview links | (from preset) | +| `post-comment` | Post results as PR comment | (from preset) | +| `create-issues` | Create GitHub issues for failures | (from preset) | +| `fail-on-error` | Fail workflow on validation errors | (from preset) | + +## Using Documentation Validation in Custom Workflows + +To use documentation validation in your own workflows: + +```yaml +jobs: + custom-docs-check: + uses: ./.github/workflows/docs-unified.yaml + with: + preset: 'pr' # Choose a preset based on your needs + # Optional overrides + check-links: false # For faster checks + notification-channels: 'slack' # For notifications +``` + +Available presets: +- `pr`: Full validation with PR comments and preview links +- `post-merge`: Lightweight link checking for merged content +- `weekly`: Comprehensive health check for scheduled runs +- `ci`: Fast validation for continuous integration + +The presets provide sensible defaults for each use case, which can be overridden as needed for specific scenarios. \ No newline at end of file diff --git a/.github/docs/WORKFLOW-ARCHITECTURE.md b/.github/docs/WORKFLOW-ARCHITECTURE.md new file mode 100644 index 0000000000000..fa758cfe98c6a --- /dev/null +++ b/.github/docs/WORKFLOW-ARCHITECTURE.md @@ -0,0 +1,333 @@ +# Documentation Workflow Architecture + +This document explains the documentation workflow architecture, which handles validation and preview processes for Coder's documentation. + +## Architecture Overview + +The documentation workflow system is built on a "pipeline" architecture, leveraging industry-standard tools and GitHub Actions best practices: + +``` +┌─ Workflow Entry Points ───────────────────────────────────────────────────────┐ +│ │ +│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌───────────┐ │ +│ │ PR Preview │ │ Post-Merge │ │ Weekly │ │ CI Check │ │ +│ │ Workflow │ │ Validation │ │ Checks │ │ Workflow │ │ +│ │ docs-preview.yml│ │ docs-ci.yml │ │weekly-docs.yml │ │docs-ci.yml│ │ +│ │ │ │ │ │ │ │ │ │ +│ │ • Runs on PR │ │ • Runs after │ │ • Runs weekly │ │ • Runs on │ │ +│ │ creation/update│ │ merges to main│ │ on schedule │ │ PR │ │ +│ │ • Generates │ │ • Checks links │ │ • Comprehensive │ │ • Basic │ │ +│ │ preview links │ │ only │ │ validation │ │ checks │ │ +│ │ • Validates docs│ │ • Falls back to │ │ • Planned: issues│ │ • Fast │ │ +│ │ • Posts comments│ │ original doc │ │ for problems │ │ feedback│ │ +│ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘ └─────┬─────┘ │ +│ │ │ │ │ │ +└───────────┼────────────────────┼────────────────────┼──────────────────┼───────┘ + │ │ │ │ + └──────────┬─────────┴──────────┬─────────┴──────────┬──────┘ + │ │ │ + ▼ ▼ ▼ +┌─ Unified Reusable Workflow ─────────────────────────────────────────────────────┐ +│ │ +│ docs-unified.yaml │ +│ ┌───────────────────────────────────────────────────────────────────────────┐ │ +│ │ Configuration System │ │ +│ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ +│ │ │ PR Preset │ │Post Preset │ │Weekly Preset│ │ CI Preset │ │ │ +│ │ └────────────┘ └────────────┘ └────────────┘ └────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────────┘ │ +│ │ +│ ┌───────────────────────────────────────────────────────────────────────────┐ │ +│ │ Validation Pipeline │ │ +│ │ ┌────────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ +│ │ │ MegaLinter │ │ Cross-reference│ │ Result Reporting│ │ │ +│ │ │ Documentation │ │ Validation │ │ and Comments │ │ │ +│ │ └────────────────┘ └────────────────┘ └────────────────┘ │ │ +│ └───────────────────────────────────────────────────────────────────────────┘ │ +│ │ +└─────────────────────────────────────────────────────────────────────────────────┘ +``` + +## Key Components + +### 1. Workflow Entry Points + +- **docs-preview.yaml**: Quick previews for PR changes +- **docs-link-check.yaml**: Post-merge validation focusing on link integrity +- **weekly-docs.yaml**: Scheduled comprehensive checks +- **docs-ci.yaml**: Fast syntax checking for CI processes + +### 2. Unified Reusable Workflow + +The `docs-unified.yaml` workflow serves as a central orchestration point: + +- Provides standardized configuration presets +- Manages file change detection using tj-actions/changed-files +- Runs validation tools through MegaLinter's documentation flavor +- Processes and reports validation results + +### 3. MegaLinter Integration + +The [MegaLinter Documentation Flavor](https://megalinter.io/latest/flavors/documentation/) provides standardized validation: + +- **markdownlint**: Checks markdown syntax and formatting +- **Vale**: Validates writing style and terminology +- **markdown-link-check**: Verifies links are valid + +### 4. Cross-Reference Validation + +Custom validation for internal document references: + +- Checks references to deleted files +- Validates internal document links +- Verifies image references exist + +## Configuration Files + +### 1. Markdownlint Configuration + +Located at `.github/docs/config/.markdownlint.yml`: + +- Controls markdown formatting and style rules +- Sets document structure requirements +- Configures allowed HTML elements + +### 2. Vale Configuration + +Located at `.github/docs/vale/.vale.ini`: + +- Defines style guide rules +- Sets alert levels and scopes +- Controls terminology enforcement + +### 3. Link Checking Configuration + +Located at `.github/docs/config/markdown-link-check.json`: + +- Defines URL patterns to ignore +- Sets timeout and retry parameters +- Configures URL replacement patterns + +## Configuration Presets + +The workflow system includes standardized configuration presets for common scenarios: + +### PR Preset + +```yaml +# For doc changes in PRs +preset: 'pr' +``` + +- Full validation with preview URLs +- PR comments with direct links to changed files +- Doesn't fail on validation errors + +### Post-Merge Preset + +```yaml +# For recent changes to main branch +preset: 'post-merge' +``` + +- Focuses on link and cross-reference validation +- Notifies about broken links (issue creation planned) +- No preview generation + +### Weekly Preset + +```yaml +# For scheduled comprehensive checks +preset: 'weekly' +``` + +- Comprehensive link checking of all documentation +- Strict validation with failure on errors +- Planned feature: GitHub issues with detailed diagnostics + +### CI Preset + +```yaml +# For fast checks during CI +preset: 'ci' +``` + +- Rapid syntax and format checking +- Minimal dependency requirements +- Fails fast on errors + +## Key Features + +### 1. MegaLinter Documentation Flavor + +- Standardized, well-maintained validation tools +- Configuration-driven rather than implementation-heavy +- Comprehensive reporting and diagnostics +- Support for multiple validation tools in one step + +### 2. File Detection with tj-actions/changed-files + +- Reliable detection of changed files +- Support for various file patterns +- Integration with GitHub's API + +### 3. Vale Style Checking + +- Consistent terminology enforcement +- Writing style validation +- Custom rules for Coder documentation + +### 4. Two-Stage PR Comments + +- Initial comment with preview links while validation runs +- Updated comment with comprehensive validation results +- Status indicators and direct links to documentation + +## Usage Examples + +### Basic Usage with Preset + +```yaml +jobs: + docs-check: + uses: ./.github/workflows/docs-unified.yaml + with: + preset: 'pr' +``` + +### Using Preset with Overrides + +```yaml +jobs: + docs-check: + uses: ./.github/workflows/docs-unified.yaml + with: + preset: 'pr' + check-links: false # Skip link checking for faster results + notification-channels: 'slack' # Add Slack notifications +``` + +### Full Custom Configuration + +```yaml +jobs: + docs-check: + uses: ./.github/workflows/docs-unified.yaml + with: + # Explicitly configure everything + lint-markdown: true + check-format: true + check-links: true + check-cross-references: true + lint-vale: true + generate-preview: true + post-comment: true + create-issues: false + fail-on-error: false + notification-channels: 'github-issue' + issue-labels: 'documentation,urgent' +``` + +## PR Comment Features + +When running with `post-comment: true` (included in the PR preset), the workflow posts a comprehensive PR comment containing: + +### 1. Status Overview + +A summary of all validation checks with clear status indicators (✅, ⚠️, ❌). + +### 2. Preview Links + +Direct links to preview your documentation changes, including: +- Main documentation +- Installation guide +- Getting Started guide +- Links to specific changed files + +### 3. Validation Stats + +Detailed statistics about the validation run: +- Number of files checked +- Percentage of successful validations +- Processing time + +## Design Decisions + +### Why MegaLinter? + +We chose MegaLinter for several reasons: + +1. **Standardization**: Uses common tool configurations +2. **Maintenance**: Regularly updated with security patches +3. **Performance**: Optimized for GitHub Actions environment +4. **Documentation**: Well-documented configuration options +5. **Extensibility**: Easy to add new linters when needed + +### Pipeline vs. Composite Action + +We moved from a composite action to a pipeline architecture because: + +1. **Isolation**: Each validation step is independent +2. **Error Handling**: Better control of failures and reporting +3. **Flexibility**: Easier to add or remove validation steps +4. **Transparency**: Clear workflow progression visible in GitHub UI + +### tj-actions/changed-files for File Detection + +We chose tj-actions/changed-files for reliable file detection: + +1. **Reliability**: Consistent results across different GitHub event types +2. **Flexibility**: Supports multiple file patterns and exclusions +3. **Maintained**: Regular updates and good support +4. **Performance**: Efficient detection without complex custom logic + +## Troubleshooting + +### Workflow Issues + +1. **File Detection Failures** + - Check the workflow logs for changed file detection results + - Verify that files match the expected patterns + - Files outside the monitored patterns will not trigger validation + +2. **MegaLinter Issues** + - Check the MegaLinter output for specific error messages + - Verify configuration file paths are correct + - Check for syntax issues in configuration files + +3. **Preview URL Issues** + - Branch names with slashes will have slashes converted to dashes + - Use the direct links provided in the PR comment + - Check if the docs preview server is properly configured + +### Local Validation + +To run validation locally before creating a PR: + +```bash +# Check markdown formatting +make lint/markdown + +# Run comprehensive validation +make lint/docs + +# Run Vale style checking (if installed) +make lint/docs-style + +# Format markdown tables +make fmt/markdown +``` + +## Future Improvements + +1. **Automated Issue Creation** + - Implement GitHub issue creation for persistent problems + - Tag appropriate teams based on issue category + +2. **Documentation Quality Metrics** + - Track documentation quality over time + - Generate reports on common issues + +3. **Integration with AI Tools** + - Implement automated fix suggestions using AI + - Add content quality analysis beyond syntax and style \ No newline at end of file diff --git a/.github/docs/config/.markdownlint.yml b/.github/docs/config/.markdownlint.yml new file mode 100644 index 0000000000000..c3a49440a16bf --- /dev/null +++ b/.github/docs/config/.markdownlint.yml @@ -0,0 +1,88 @@ +# Markdownlint configuration for Coder documentation +# Based on markdownlint defaults with some customizations + +# MD001 - Heading levels should only increment by one level at a time +MD001: true + +# MD003 - Heading style +MD003: + style: "atx" # Use # style headers + +# MD004 - Unordered list style +MD004: + style: "dash" # Use - for unordered lists + +# MD007 - Unordered list indentation +MD007: + indent: 2 # Use 2 spaces for list indentation + +# MD009 - Trailing spaces +MD009: + br_spaces: 2 # Allow 2 spaces for line breaks + list_item_empty_lines: false # Don't allow trailing spaces on empty lines in lists + +# MD010 - Hard tabs +MD010: true # No hard tabs + +# MD012 - Multiple consecutive blank lines +MD012: + maximum: 1 # Allow at most 1 consecutive blank line + +# MD013 - Line length +MD013: + line_length: 120 # Allow longer lines for docs + heading_line_length: 120 + code_block_line_length: 120 + tables: false # Don't check tables + +# MD018 - No space after hash on atx style heading +MD018: true + +# MD022 - Headings should be surrounded by blank lines +MD022: true + +# MD023 - Headings must start at the beginning of the line +MD023: true + +# MD025 - Single title/h1 per document +MD025: + level: 1 # Only check level 1 headings + front_matter_title: "" # Don't check title in front matter + +# MD026 - Trailing punctuation in heading +MD026: + punctuation: ".,;:!。,;:!" # Default punctuation + +# MD029 - Ordered list item prefix +MD029: + style: "one_or_ordered" # Allow 1. 2. 3. or 1. 1. 1. + +# MD033 - Inline HTML +MD033: + allowed_elements: ["br", "img", "a", "details", "summary", "kbd", "div", "span", "sup", "sub", "Image", "br"] # Allow certain HTML elements + +# MD034 - Bare URL should be in angle brackets or Markdown link +MD034: true + +# MD036 - Emphasis used instead of heading +MD036: false # Allow emphasis for non-heading purposes + +# MD040 - Fenced code blocks should have a language specified +MD040: true + +# MD041 - First line in a file should be a top-level heading +MD041: + level: 1 # Only check for level 1 headings + front_matter_title: "^\\s*title\\s*:" # Title in front matter + +# MD043 - Required heading structure +MD043: false # Don't require specific heading structure + +# MD044 - Proper names should have consistent capitalization +MD044: + names: ["Coder", "GitHub", "Docker", "Kubernetes", "JavaScript", "TypeScript", "React", "SSH", "API"] # List of proper names + code_blocks: false # Don't check code blocks + +# MD046 - Code block style +MD046: + style: "fenced" # Use ```code``` style \ No newline at end of file diff --git a/.github/docs/config/README.md b/.github/docs/config/README.md new file mode 100644 index 0000000000000..53ff594b24feb --- /dev/null +++ b/.github/docs/config/README.md @@ -0,0 +1,52 @@ +# Documentation Tool Configuration + +This directory contains configuration files for the various documentation validation tools used in our workflow. + +## Files + +- `.markdownlint.yml`: Configuration for markdownlint rules +- `markdown-link-check.json`: Configuration for markdown-link-check tool + +## Integration with MegaLinter + +These configuration files are used by MegaLinter's documentation flavor. MegaLinter provides a standardized way to run various linters with consistent configuration and reporting. + +## Markdownlint Configuration + +The `.markdownlint.yml` file controls the rules for markdown linting, including: + +- Heading structure and formatting +- List formatting and consistency +- Permitted HTML elements +- Line length requirements +- Code block formatting + +## Link Checking Configuration + +The `markdown-link-check.json` file configures link validation: + +- URL patterns to ignore (anchors, mailto, etc.) +- URL replacement patterns +- Timeout and retry settings +- Valid status codes + +## Local Usage + +To run these tools locally: + +```bash +# Run markdown linting +make lint/markdown + +# Run full documentation validation (including links) +make lint/docs +``` + +## Adding or Modifying Rules + +When modifying configuration files: + +1. Test changes locally first +2. Document any significant changes in the PR +3. Consider impacts on existing documentation +4. Run the test script to validate integration: `.github/docs/testing/test-megalinter.sh` \ No newline at end of file diff --git a/.github/docs/config/markdown-link-check.json b/.github/docs/config/markdown-link-check.json new file mode 100644 index 0000000000000..c8630af4e0baf --- /dev/null +++ b/.github/docs/config/markdown-link-check.json @@ -0,0 +1,27 @@ +{ + "ignorePatterns": [ + { + "pattern": "^#" + }, + { + "pattern": "^mailto:" + }, + { + "pattern": "^\\.\\./" + }, + { + "pattern": "^https://coder.com/docs/@" + } + ], + "replacementPatterns": [ + { + "pattern": "^/", + "replacement": "{{BASEURL}}/" + } + ], + "timeout": "10s", + "retryOn429": true, + "retryCount": 3, + "fallbackRetryDelay": "30s", + "aliveStatusCodes": [200, 203, 206] +} \ No newline at end of file diff --git a/.github/docs/testing/README.md b/.github/docs/testing/README.md new file mode 100644 index 0000000000000..ce04680acfb21 --- /dev/null +++ b/.github/docs/testing/README.md @@ -0,0 +1,46 @@ +# Documentation Workflow Testing + +This directory contains utilities for testing the documentation validation workflows locally. + +## Vale Testing + +The `test-vale.sh` script is designed to validate that the Vale integration in the unified documentation workflow functions correctly. This script simulates the GitHub Actions environment and tests the full Vale style checking approach, including: + +1. Installation of Vale using the same method as the workflow +2. Basic Vale execution and verification +3. JSON output format validation +4. Chunked processing of multiple files (as implemented in the workflow) + +### Running the Test + +To run the Vale integration test: + +```bash +cd .github/docs/testing +./test-vale.sh +``` + +### What the Test Covers + +- Validates that Vale can be properly installed and run +- Confirms that Vale produces valid JSON output +- Tests the chunked processing approach used in the workflow +- Verifies the JSON results combination logic + +### When to Run This Test + +Run this test when: + +1. Making changes to the Vale integration in the docs-core action +2. Upgrading the Vale version used in the workflow +3. Modifying the Vale configuration or style rules +4. Troubleshooting Vale-related issues in the GitHub Actions environment + +## Implementation Notes + +The Vale integration in our workflow has two components: + +1. Installation in the parent workflow (`docs-unified.yaml`) +2. Execution in the docs-core composite action + +This approach ensures that Vale is properly installed and accessible to the composite action without requiring it to download and install Vale itself, which could be unreliable within the composite action context. \ No newline at end of file diff --git a/.github/docs/testing/test-megalinter.sh b/.github/docs/testing/test-megalinter.sh new file mode 100755 index 0000000000000..65598ff8a4676 --- /dev/null +++ b/.github/docs/testing/test-megalinter.sh @@ -0,0 +1,128 @@ +#!/bin/bash +# Test script for MegaLinter integration in docs workflow +set -e + +echo "Testing MegaLinter documentation validation integration" +echo "------------------------------------------------------" + +# Function to clean up on exit +cleanup() { + echo "Cleaning up..." + rm -rf "$TEMP_DIR" +} + +# Create temporary directory +TEMP_DIR=$(mktemp -d) +trap cleanup EXIT + +# Setup test environment +DOCS_DIR="$TEMP_DIR/docs" +CONFIG_DIR="$TEMP_DIR/.github/docs/config" +VALE_DIR="$TEMP_DIR/.github/docs/vale/styles/Coder" + +mkdir -p "$DOCS_DIR" +mkdir -p "$CONFIG_DIR" +mkdir -p "$VALE_DIR" + +# Copy configuration files for testing +echo "Copying configuration files..." +cp -r ".github/docs/config/.markdownlint.yml" "$CONFIG_DIR/" +cp -r ".github/docs/vale" "$TEMP_DIR/.github/docs/" + +# Create a sample markdown file with issues +cat >"$DOCS_DIR/sample.md" <"$TEMP_DIR/changed_files.json" + +echo "=== PHASE 1: Testing markdownlint ===" +echo "-----------------------------------------" + +if command -v markdownlint-cli2 &>/dev/null; then + echo "Testing markdownlint-cli2 on sample document..." + pnpm exec markdownlint-cli2 "$DOCS_DIR/sample.md" || echo "✅ Found markdown issues as expected" +else + echo "⚠️ markdownlint-cli2 not available, skipping test" +fi + +echo +echo "=== PHASE 2: Testing Vale ===" +echo "-----------------------------" + +if command -v vale &>/dev/null; then + echo "Testing Vale on sample document..." + vale --output=line --config=".github/docs/vale/.vale.ini" "$DOCS_DIR/sample.md" || echo "✅ Found style issues as expected" +else + echo "⚠️ Vale not available, skipping test" +fi + +echo +echo "=== PHASE 3: Testing markdown-link-check ===" +echo "--------------------------------------------" + +if command -v markdown-link-check &>/dev/null; then + echo "Testing markdown-link-check on sample document..." + markdown-link-check "$DOCS_DIR/sample.md" || echo "✅ Found link issues as expected" +else + echo "⚠️ markdown-link-check not available, skipping test" +fi + +echo +echo "=== PHASE 4: Testing cross-reference validation ===" +echo "--------------------------------------------------" + +# Create a function to simulate the cross-reference validation logic +check_cross_references() { + local docs_dir="$1" + + echo "Checking cross-references in $docs_dir..." + + # Check for broken internal links + for file in "$docs_dir"/*.md; do + echo "Checking $file for broken references..." + # Extract markdown links that aren't http/https + if grep -q -E '\[[^]]+\]\(nonexistent.md\)' "$file"; then + echo "Found broken reference to nonexistent.md in $file" + echo "✅ Found cross-reference issues as expected" + return 0 + fi + done + + echo "❌ No cross-reference issues found when issues were expected" + return 1 +} + +# Run cross-reference check +check_cross_references "$DOCS_DIR" + +echo +echo "=== TEST SUMMARY ===" +echo "All validation tests completed successfully! 🎉" +echo +echo "This script verified the core functionality used in the MegaLinter-based docs workflow:" +echo "1. markdownlint syntax and format checking" +echo "2. Vale style checking" +echo "3. Link validation" +echo "4. Cross-reference checking" +echo +echo "The workflow is properly configured to use standardized tools through MegaLinter's documentation flavor" diff --git a/.github/docs/vale/.vale.ini b/.github/docs/vale/.vale.ini new file mode 100644 index 0000000000000..b9fb4fac7a824 --- /dev/null +++ b/.github/docs/vale/.vale.ini @@ -0,0 +1,16 @@ +# Vale configuration for Coder documentation +StylesPath = .github/docs/vale/styles + +# Minimum alert level +MinAlertLevel = suggestion + +# Files to analyze +[*.md] +# Vale styles to use +BasedOnStyles = Coder, GitLab + +# Disable these specific rules +# Coder.Terms = YES/NO + +# Spell checking via Vale +TokenIgnores = (\w+://\S+) # Ignore URLs \ No newline at end of file diff --git a/.github/docs/vale/README.md b/.github/docs/vale/README.md new file mode 100644 index 0000000000000..1eb6c140da1fc --- /dev/null +++ b/.github/docs/vale/README.md @@ -0,0 +1,90 @@ +# Vale Style Checking for Coder Documentation + +This directory contains configuration files and custom style rules for Vale, a syntax-aware linter for prose. + +## Integration with MegaLinter + +Vale is integrated into our documentation workflow through MegaLinter's documentation flavor. This approach provides: + +1. Standardized configuration and execution +2. Consistent reporting format +3. Integration with other linting tools + +## Configuration + +The primary Vale configuration is in `.vale.ini`, which: + +- Sets style baselines and alert levels +- Configures which types of content to ignore +- Defines which style rules to apply + +## Custom Style Rules + +We maintain several custom style rule sets: + +### Coder Style + +Located in `styles/Coder/`: + +- `Headings.yml`: Ensures proper heading capitalization +- `Terms.yml`: Enforces consistent terminology +- `RepeatedWords.yml`: Catches repeated words like "the the" +- `SentenceLength.yml`: Warns about overly long sentences + +### GitLab Style + +We also include selected rules from GitLab's style guide in `styles/GitLab/`. + +## Using Vale Locally + +### Installation + +```bash +# macOS +brew install vale + +# Linux +curl -sfL https://github.com/errata-ai/vale/releases/download/v2.30.0/vale_2.30.0_Linux_64-bit.tar.gz | tar -xz -C ~/.local/bin vale +chmod +x ~/.local/bin/vale + +# Windows (with Chocolatey) +choco install vale +``` + +### Running Vale + +```bash +# Option 1: Use make target (preferred) +make lint/docs-style + +# Option 2: Run Vale directly +vale --config=.github/docs/vale/.vale.ini docs/ + +# Option 3: Check specific files +vale --config=.github/docs/vale/.vale.ini path/to/file.md +``` + +## Suppressing Vale Warnings + +To suppress Vale warnings in specific sections of a document: + +```markdown + +This is a very long sentence that would normally trigger the sentence length rule but has been explicitly exempted for a good reason such as a technical requirement or quotation. + +``` + +To disable Vale for an entire file, add this to the frontmatter: + +```markdown +--- +vale: false +--- +``` + +## Adding New Rules + +1. Create a new YAML file in the appropriate style directory +2. Follow the [Vale package format](https://vale.sh/docs/topics/packages/) +3. Test your rule with `vale --config=.github/docs/vale/.vale.ini --glob='*.md' /path/to/test/file.md` +4. Update this README with information about your new rule \ No newline at end of file diff --git a/.github/docs/vale/styles/Coder/Headings.yml b/.github/docs/vale/styles/Coder/Headings.yml new file mode 100644 index 0000000000000..08fccd73dcce4 --- /dev/null +++ b/.github/docs/vale/styles/Coder/Headings.yml @@ -0,0 +1,14 @@ +extends: capitalization +message: "'%s' should use title case" +level: warning +scope: heading +match: $title +style: AP # The Associated Press Style Guide +exceptions: + - Coder + - CLI + - API + - UI + - SSH + - URLs + - IP \ No newline at end of file diff --git a/.github/docs/vale/styles/Coder/RepeatedWords.yml b/.github/docs/vale/styles/Coder/RepeatedWords.yml new file mode 100644 index 0000000000000..d2743b400d7a8 --- /dev/null +++ b/.github/docs/vale/styles/Coder/RepeatedWords.yml @@ -0,0 +1,7 @@ +extends: repetition +message: "'%s' is repeated" +level: error +scope: paragraph +ignorecase: true +tokens: + - '[a-z]+' \ No newline at end of file diff --git a/.github/docs/vale/styles/Coder/SentenceLength.yml b/.github/docs/vale/styles/Coder/SentenceLength.yml new file mode 100644 index 0000000000000..7654d5848fefd --- /dev/null +++ b/.github/docs/vale/styles/Coder/SentenceLength.yml @@ -0,0 +1,7 @@ +extends: metric +message: "Try to keep sentences under 30 words." +level: suggestion +scope: sentence +formula: | + size(tokens) +condition: "> 30" \ No newline at end of file diff --git a/.github/docs/vale/styles/Coder/Terms.yml b/.github/docs/vale/styles/Coder/Terms.yml new file mode 100644 index 0000000000000..4abab4008ba18 --- /dev/null +++ b/.github/docs/vale/styles/Coder/Terms.yml @@ -0,0 +1,13 @@ +extends: substitution +message: "Use '%s' instead of '%s'" +level: error +ignorecase: false +swap: + "(?> $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 + env: + INPUT_PR_NUMBER: ${{ inputs.pr-number }} + GITHUB_EVENT_NAME: ${{ github.event_name }} + GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }} + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + shell: bash + run: | + echo "::group::Extracting context information" + + # Extract PR number from inputs or context + if [ -n "$INPUT_PR_NUMBER" ]; then + PR_NUMBER="$INPUT_PR_NUMBER" + echo "::notice::Using PR number from action input: #${PR_NUMBER}" + elif [ "$GITHUB_EVENT_NAME" == "pull_request" ]; then + PR_NUMBER="$GITHUB_PR_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: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ inputs.pr-number || github.event.pull_request.number }} + body: | + # 📚 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) + + ⏳ Waiting for validation results... + reactions: eyes + + # Get changed files + - name: Get changed files + id: changed-files + uses: tj-actions/changed-files@v41 + with: + files: | + docs/**/*.md + **/*.md + + # Update PR comment to show we're starting validation + - name: Update PR comment with in-progress status + if: inputs.post-comment == 'true' && (inputs.pr-number != '' || github.event.pull_request) && steps.changed-files.outputs.all_changed_files != '' + uses: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + issue-number: ${{ inputs.pr-number || github.event.pull_request.number }} + body: | + # 📚 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) + + ⏳ Found ${{ steps.changed-files.outputs.all_changed_files_count }} markdown files. Validating documentation... + reactions: eyes + + # 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" + + # Variables for template + SANITIZED_BRANCH="${{ steps.context-info.outputs.sanitized_branch }}" + PREVIEW_URL="https://coder.com/docs/@$SANITIZED_BRANCH" + BADGE="${{ steps.validation-results.outputs.badge }}" + FILES="${{ steps.changed-files.outputs.all_changed_files_count }}" + SUCCESS="${{ steps.validation-results.outputs.success_percentage }}" + PASSING="${{ steps.validation-results.outputs.passing_count }}" + TOTAL="${{ steps.validation-results.outputs.validation_count }}" + DURATION="${{ steps.validation-duration.outputs.duration }}" + WORKFLOW_RUN="${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + # Create header based on success + if [ "${{ steps.validation-results.outputs.overall_success }}" == "true" ]; then + HEADER="# 📚 Documentation Preview ✅" + EMOJI="✅" + else + HEADER="# 📚 Documentation Preview ⚠️" + EMOJI="⚠️" + fi + + # Create comment content + COMMENT="$HEADER + +## 🖥️ [View Documentation Preview]($PREVIEW_URL) + +> $EMOJI **Validation Result**: $BADGE + +### Quick Links +- [Main Docs]($PREVIEW_URL) +- [Installation Guide]($PREVIEW_URL/install) +- [Quickstart]($PREVIEW_URL/tutorials/quickstart) + +### 📊 Validation Stats + +- **Changed Files**: $FILES files checked +- **Validation Success**: $SUCCESS% ($PASSING/$TOTAL checks passed) +- **Processing Time**: $DURATION + +⏱️ Validation completed in $DURATION | [View Workflow Run]($WORKFLOW_RUN)" + + # Export content to GitHub output + echo "comment<> $GITHUB_OUTPUT + echo "$COMMENT" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + echo "::endgroup::" + + # Find existing comment + - name: Find existing comment + if: inputs.post-comment == 'true' && (inputs.pr-number != '' || github.event.pull_request) && steps.changed-files.outputs.all_changed_files != '' + uses: peter-evans/find-comment@3eae4d37986fb5a8592848f6a574fdf654e61f9e # v3.1.0 + id: find-comment + with: + issue-number: ${{ inputs.pr-number || github.event.pull_request.number }} + comment-author: "github-actions[bot]" + body-includes: "Documentation Preview" + direction: last + + # 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: peter-evans/create-or-update-comment@71345be0265236311c031f5c7866368bd1eff043 # v4.0.0 + with: + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ inputs.pr-number || github.event.pull_request.number }} + body: ${{ steps.prepare-comment.outputs.comment }} + edit-mode: replace + reactions: ${{ steps.validation-results.outputs.overall_success == 'true' && 'rocket' || 'eyes' }} + + # 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