From 67b1ff8229e5ae93af0c96a85cd2c81f4462f373 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 17 Jan 2026 04:21:42 +0000
Subject: [PATCH 1/4] Initial plan
From adde8010b6d1c4a31fc7ed9be8f66843949fb276 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 17 Jan 2026 04:49:58 +0000
Subject: [PATCH 2/4] ci: add documentation preview workflow for PRs
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
---
.github/workflows/docs-preview.yml | 192 +++++++++++++++++++++++++++++
1 file changed, 192 insertions(+)
create mode 100644 .github/workflows/docs-preview.yml
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
new file mode 100644
index 000000000..afcd11eda
--- /dev/null
+++ b/.github/workflows/docs-preview.yml
@@ -0,0 +1,192 @@
+name: Documentation Preview
+
+on:
+ pull_request:
+ branches: [main]
+ paths:
+ - 'docs-site/**'
+ - 'docs/**'
+ - '.github/workflows/docs-preview.yml'
+ pull_request_target:
+ types: [closed]
+ paths:
+ - 'docs-site/**'
+ - 'docs/**'
+
+permissions:
+ contents: read
+ pull-requests: write
+ deployments: write
+
+concurrency:
+ group: docs-preview-${{ github.event.pull_request.number || github.run_id }}
+ cancel-in-progress: true
+
+jobs:
+ build-and-preview:
+ name: Build and Deploy Preview
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request' && github.event.action != 'closed'
+ timeout-minutes: 10
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: docs-site/package-lock.json
+
+ - name: Install dependencies
+ run: |
+ cd docs-site
+ npm ci
+
+ - name: Build documentation for preview
+ run: |
+ cd docs-site
+ npm run build
+ env:
+ # Use PR-specific base path for preview
+ NODE_ENV: production
+
+ - name: Upload preview artifact
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ id: upload-artifact
+ with:
+ name: docs-preview-pr-${{ github.event.pull_request.number }}
+ path: docs-site/dist
+ retention-days: 7
+
+ - name: Create deployment
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ id: deployment
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const { data: deployment } = await github.rest.repos.createDeployment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ ref: context.payload.pull_request.head.sha,
+ environment: `docs-preview-pr-${context.payload.pull_request.number}`,
+ auto_merge: false,
+ required_contexts: [],
+ transient_environment: true,
+ production_environment: false,
+ description: `Documentation preview for PR #${context.payload.pull_request.number}`
+ });
+
+ // Set deployment status to success with artifact link
+ const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts`;
+
+ await github.rest.repos.createDeploymentStatus({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ deployment_id: deployment.id,
+ state: 'success',
+ environment_url: artifactUrl,
+ log_url: `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
+ description: 'Documentation preview is ready'
+ });
+
+ return { deploymentId: deployment.id, artifactUrl };
+
+ - name: Comment on PR
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const prNumber = context.payload.pull_request.number;
+ const runId = context.runId;
+ const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/artifacts`;
+
+ const commentBody = `## 📚 Documentation Preview
+
+ A preview of your documentation changes has been built and is available for download.
+
+ | Resource | Link |
+ |----------|------|
+ | 📦 **Preview Artifact** | [Download docs-preview-pr-${prNumber}](${artifactUrl}) |
+ | 🔧 **Workflow Run** | [View Build Logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}) |
+
+
+ How to view the preview
+
+ 1. Download the artifact from the link above
+ 2. Extract the zip file
+ 3. Open \`index.html\` in your browser, or serve locally with:
+ \`\`\`bash
+ npx serve ./dist
+ \`\`\`
+
+
+
+ ---
+ *Preview generated from commit ${context.payload.pull_request.head.sha.substring(0, 7)}*`;
+
+ // Find existing preview comment
+ const { data: comments } = await github.rest.issues.listComments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ });
+
+ const botComment = comments.find(comment =>
+ comment.user.type === 'Bot' &&
+ comment.body.includes('Documentation Preview')
+ );
+
+ if (botComment) {
+ // Update existing comment
+ await github.rest.issues.updateComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ comment_id: botComment.id,
+ body: commentBody
+ });
+ } else {
+ // Create new comment
+ await github.rest.issues.createComment({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ issue_number: prNumber,
+ body: commentBody
+ });
+ }
+
+ cleanup:
+ name: Cleanup Preview Deployment
+ runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request_target' && github.event.action == 'closed'
+
+ steps:
+ - name: Deactivate deployment
+ uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ script: |
+ const prNumber = context.payload.pull_request.number;
+ const environmentName = `docs-preview-pr-${prNumber}`;
+
+ // List deployments for this PR's environment
+ const { data: deployments } = await github.rest.repos.listDeployments({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ environment: environmentName
+ });
+
+ // Set all deployments to inactive
+ for (const deployment of deployments) {
+ await github.rest.repos.createDeploymentStatus({
+ owner: context.repo.owner,
+ repo: context.repo.repo,
+ deployment_id: deployment.id,
+ state: 'inactive',
+ description: `PR #${prNumber} was closed`
+ });
+ }
+
+ console.log(`Deactivated ${deployments.length} deployment(s) for environment: ${environmentName}`);
From b1cca332ad44ea101476d7329e96448bdb6d36ce Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 17 Jan 2026 04:51:51 +0000
Subject: [PATCH 3/4] ci: address review comments for docs preview workflow
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
---
.github/workflows/docs-preview.yml | 7 ++-----
1 file changed, 2 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index afcd11eda..f15f43de3 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -9,9 +9,6 @@ on:
- '.github/workflows/docs-preview.yml'
pull_request_target:
types: [closed]
- paths:
- - 'docs-site/**'
- - 'docs/**'
permissions:
contents: read
@@ -30,7 +27,7 @@ jobs:
timeout-minutes: 10
steps:
- - name: Checkout
+ - name: Checkout PR source code
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
- name: Setup Node.js
@@ -119,7 +116,7 @@ jobs:
2. Extract the zip file
3. Open \`index.html\` in your browser, or serve locally with:
\`\`\`bash
- npx serve ./dist
+ npx serve .
\`\`\`
From eef90e18affccfe497c4d90ff3aec8dbf0379b03 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 17 Jan 2026 09:15:22 +0000
Subject: [PATCH 4/4] ci: convert docs preview to agentic workflow with
playwright
Co-authored-by: Mossaka <5447827+Mossaka@users.noreply.github.com>
---
.github/workflows/docs-preview.yml | 137 +++++++++++++++++++++++++----
1 file changed, 118 insertions(+), 19 deletions(-)
diff --git a/.github/workflows/docs-preview.yml b/.github/workflows/docs-preview.yml
index f15f43de3..927676012 100644
--- a/.github/workflows/docs-preview.yml
+++ b/.github/workflows/docs-preview.yml
@@ -3,10 +3,6 @@ name: Documentation Preview
on:
pull_request:
branches: [main]
- paths:
- - 'docs-site/**'
- - 'docs/**'
- - '.github/workflows/docs-preview.yml'
pull_request_target:
types: [closed]
@@ -20,11 +16,13 @@ concurrency:
cancel-in-progress: true
jobs:
- build-and-preview:
- name: Build and Deploy Preview
+ build-preview:
+ name: Build Documentation Preview
runs-on: ubuntu-latest
if: github.event_name == 'pull_request' && github.event.action != 'closed'
timeout-minutes: 10
+ outputs:
+ artifact-url: ${{ steps.upload-artifact.outputs.artifact-url }}
steps:
- name: Checkout PR source code
@@ -47,7 +45,6 @@ jobs:
cd docs-site
npm run build
env:
- # Use PR-specific base path for preview
NODE_ENV: production
- name: Upload preview artifact
@@ -76,7 +73,6 @@ jobs:
description: `Documentation preview for PR #${context.payload.pull_request.number}`
});
- // Set deployment status to success with artifact link
const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/artifacts`;
await github.rest.repos.createDeploymentStatus({
@@ -91,38 +87,129 @@ jobs:
return { deploymentId: deployment.id, artifactUrl };
- - name: Comment on PR
+ preview-with-copilot:
+ name: Generate Documentation Preview with Copilot
+ runs-on: ubuntu-latest
+ needs: build-preview
+ timeout-minutes: 15
+
+ steps:
+ - name: Checkout PR source code
+ uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4
+
+ - name: Setup Node.js
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
+ with:
+ node-version: '20'
+
+ - name: Download preview artifact
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
+ with:
+ name: docs-preview-pr-${{ github.event.pull_request.number }}
+ path: docs-preview
+
+ - name: Install serve
+ run: npm install -g serve
+
+ - name: Start local preview server
+ run: |
+ serve docs-preview -l 3000 &
+ echo "SERVER_PID=$!" >> "$GITHUB_ENV"
+ # Wait for server to be ready
+ sleep 3
+
+ - name: Generate preview screenshot and comment
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
+ const { execSync } = require('child_process');
+ const fs = require('fs');
+ const path = require('path');
+
+ // Install Playwright
+ execSync('npx playwright install chromium', { stdio: 'inherit' });
+
+ // Create screenshot script
+ const screenshotScript = `
+ const { chromium } = require('playwright');
+
+ (async () => {
+ const browser = await chromium.launch();
+ const page = await browser.newPage();
+ await page.setViewportSize({ width: 1280, height: 800 });
+
+ // Navigate to the local preview
+ await page.goto('http://localhost:3000/gh-aw-firewall/', {
+ waitUntil: 'networkidle',
+ timeout: 30000
+ });
+
+ // Wait for content to load
+ await page.waitForTimeout(2000);
+
+ // Take screenshot
+ await page.screenshot({
+ path: 'docs-preview-screenshot.png',
+ fullPage: false
+ });
+
+ // Get page title for the comment
+ const title = await page.title();
+ console.log('PAGE_TITLE=' + title);
+
+ await browser.close();
+ })();
+ `;
+
+ fs.writeFileSync('take-screenshot.js', screenshotScript);
+
+ // Run screenshot script
+ const output = execSync('node take-screenshot.js', { encoding: 'utf-8' });
+ const titleMatch = output.match(/PAGE_TITLE=(.+)/);
+ const pageTitle = titleMatch ? titleMatch[1] : 'Documentation Preview';
+
const prNumber = context.payload.pull_request.number;
const runId = context.runId;
const artifactUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}/artifacts`;
+ const commitSha = context.payload.pull_request.head.sha.substring(0, 7);
+ // Create comment with link to screenshot artifact
const commentBody = `## 📚 Documentation Preview
- A preview of your documentation changes has been built and is available for download.
+ A preview of the documentation has been built and reviewed using Playwright.
+
+ ### 📸 Preview Screenshot
+
+ The documentation homepage screenshot has been captured and is available as an artifact.
+
+ **Page Title:** ${pageTitle}
+
+ > 💡 Download the \`docs-preview-screenshot-pr-${prNumber}\` artifact to view the screenshot.
+
+ ### Resources
| Resource | Link |
|----------|------|
- | 📦 **Preview Artifact** | [Download docs-preview-pr-${prNumber}](${artifactUrl}) |
+ | 📷 **Screenshot** | [Download Screenshot](${artifactUrl}) |
+ | 📦 **Full Preview** | [Download docs-preview-pr-${prNumber}](${artifactUrl}) |
| 🔧 **Workflow Run** | [View Build Logs](https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${runId}) |
- How to view the preview
+ How to view the full preview locally
- 1. Download the artifact from the link above
+ 1. Download the \`docs-preview-pr-${prNumber}\` artifact from the link above
2. Extract the zip file
- 3. Open \`index.html\` in your browser, or serve locally with:
+ 3. Serve locally with:
\`\`\`bash
npx serve .
\`\`\`
+ 4. Open http://localhost:3000/gh-aw-firewall/ in your browser
---
- *Preview generated from commit ${context.payload.pull_request.head.sha.substring(0, 7)}*`;
+ *Preview generated from commit ${commitSha} using Playwright*`;
// Find existing preview comment
const { data: comments } = await github.rest.issues.listComments({
@@ -137,7 +224,6 @@ jobs:
);
if (botComment) {
- // Update existing comment
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -145,7 +231,6 @@ jobs:
body: commentBody
});
} else {
- // Create new comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
@@ -153,6 +238,22 @@ jobs:
body: commentBody
});
}
+
+ console.log('Documentation preview comment posted successfully!');
+
+ - name: Upload screenshot artifact
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
+ with:
+ name: docs-preview-screenshot-pr-${{ github.event.pull_request.number }}
+ path: docs-preview-screenshot.png
+ retention-days: 7
+
+ - name: Stop preview server
+ if: always()
+ run: |
+ if [ -n "$SERVER_PID" ]; then
+ kill "$SERVER_PID" || true
+ fi
cleanup:
name: Cleanup Preview Deployment
@@ -168,14 +269,12 @@ jobs:
const prNumber = context.payload.pull_request.number;
const environmentName = `docs-preview-pr-${prNumber}`;
- // List deployments for this PR's environment
const { data: deployments } = await github.rest.repos.listDeployments({
owner: context.repo.owner,
repo: context.repo.repo,
environment: environmentName
});
- // Set all deployments to inactive
for (const deployment of deployments) {
await github.rest.repos.createDeploymentStatus({
owner: context.repo.owner,