chore: refresh docs pages publishing flow

This commit is contained in:
snomiao
2025-10-14 03:58:38 +00:00
parent cd714151fd
commit 807a395b29
10 changed files with 485 additions and 194 deletions

View File

@@ -4,11 +4,15 @@ permissions:
contents: read
pages: write
id-token: write
actions: read
on:
push:
branches:
- main
# Trigger after successful CI workflows complete
workflow_run:
workflows: ["Storybook and Chromatic CI", "Vitest Tests", "Tests CI"]
types: [completed]
branches: [main]
# Keep manual trigger for debugging
workflow_dispatch:
concurrency:
@@ -18,9 +22,11 @@ concurrency:
jobs:
build:
runs-on: ubuntu-latest
# Only run if the triggering workflow succeeded (or manual dispatch)
if: github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success'
steps:
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v5
- name: Install pnpm
uses: pnpm/action-setup@v4
@@ -28,7 +34,7 @@ jobs:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v5
with:
node-version: '24'
cache: 'pnpm'
@@ -36,194 +42,77 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build Storybook
run: pnpm build-storybook --output-dir dist/storybook
- name: Build Nx Graph
run: |
mkdir -p dist/nx-graph
pnpm nx graph --file=dist/nx-graph/index.html || echo "Nx graph generation skipped"
- name: Download Storybook artifact from latest CI run
continue-on-error: true
- name: Build Vitest UI
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p dist/vitest-ui
pnpm test:unit --run --reporter=html --reporter=json --outputFile.html=dist/vitest-ui/index.html --outputFile.json=dist/vitest-ui/results.json || echo "Vitest UI generation skipped"
echo "🔍 Downloading latest Storybook artifact..."
# Get latest successful Storybook workflow run
LATEST_RUN=$(gh api "repos/${{ github.repository }}/actions/workflows" \
--jq '.workflows[] | select(.name == "Storybook and Chromatic CI") | .id' | head -1)
if [ -n "$LATEST_RUN" ]; then
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/$LATEST_RUN/runs?status=success&branch=main" \
--jq '.workflow_runs[0].id' 2>/dev/null || echo "")
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then
# Download storybook-static artifact
ARTIFACT_ID=$(gh api "repos/${{ github.repository }}/actions/runs/$RUN_ID/artifacts" \
--jq '.artifacts[] | select(.name == "storybook-static") | .id' | head -1)
if [ -n "$ARTIFACT_ID" ]; then
gh api "repos/${{ github.repository }}/actions/artifacts/$ARTIFACT_ID/zip" > storybook-static.zip
unzip -q storybook-static.zip -d ./storybook-static
echo "✅ Downloaded and extracted Storybook artifact"
else
echo "⚠️ Storybook artifact not found, will build from source"
fi
else
echo "⚠️ No successful Storybook runs found, will build from source"
fi
else
echo "⚠️ Storybook workflow not found, will build from source"
fi
- name: Download Vitest reports from latest CI run
continue-on-error: true
- name: Generate test coverage
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p dist/coverage
pnpm test:unit --run --coverage --coverage.reporter=html --coverage.reportsDirectory=dist/coverage || echo "Coverage generation skipped"
continue-on-error: true
echo "🔍 Downloading latest Vitest reports..."
- name: Generate Knip report
run: |
mkdir -p dist/knip
pnpm knip --reporter json > dist/knip/report.json || echo "{}" > dist/knip/report.json
echo '<!DOCTYPE html><html><head><meta charset="UTF-8"><title>Knip Report</title><style>body{font-family:monospace;padding:2rem;background:#1a1a1a;color:#f0f0f0}pre{background:#2a2a2a;padding:1rem;border-radius:8px;overflow-x:auto}</style></head><body><h1>Knip Report</h1><pre>' > dist/knip/index.html
cat dist/knip/report.json >> dist/knip/index.html
echo '</pre></body></html>' >> dist/knip/index.html
continue-on-error: true
# Get latest successful Vitest workflow run
LATEST_RUN=$(gh api "repos/${{ github.repository }}/actions/workflows" \
--jq '.workflows[] | select(.name == "Vitest Tests") | .id' | head -1)
- name: Create index page
run: |
cat > dist/index.html << 'EOF'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ComfyUI Frontend - Development Tools</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
}
.container {
max-width: 1200px;
width: 100%;
}
.header {
text-align: center;
color: white;
margin-bottom: 3rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
.header p {
font-size: 1.125rem;
opacity: 0.9;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
text-decoration: none;
color: inherit;
display: block;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
}
.card-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.card-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #1a202c;
}
.card-description {
color: #718096;
line-height: 1.6;
}
.card-status {
display: inline-block;
margin-top: 1rem;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
.status-available {
background: #c6f6d5;
color: #22543d;
}
.status-unavailable {
background: #fed7d7;
color: #742a2a;
}
.footer {
text-align: center;
color: white;
margin-top: 3rem;
opacity: 0.8;
}
.footer a {
color: white;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎨 ComfyUI Frontend</h1>
<p>Development Tools & Documentation</p>
</div>
if [ -n "$LATEST_RUN" ]; then
RUN_ID=$(gh api "repos/${{ github.repository }}/actions/workflows/$LATEST_RUN/runs?status=success&branch=main" \
--jq '.workflow_runs[0].id' 2>/dev/null || echo "")
<div class="grid">
<a href="./storybook/index.html" class="card">
<div class="card-icon">📚</div>
<h2 class="card-title">Storybook</h2>
<p class="card-description">Interactive component library and design system documentation</p>
<span class="card-status status-available">Available</span>
</a>
if [ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ]; then
# Download vitest-reports artifact
ARTIFACT_ID=$(gh api "repos/${{ github.repository }}/actions/runs/$RUN_ID/artifacts" \
--jq '.artifacts[] | select(.name == "vitest-reports") | .id' | head -1)
<a href="./nx-graph/index.html" class="card">
<div class="card-icon">🔗</div>
<h2 class="card-title">Nx Dependency Graph</h2>
<p class="card-description">Visual representation of project dependencies and build structure</p>
<span class="card-status status-available">Available</span>
</a>
if [ -n "$ARTIFACT_ID" ]; then
mkdir -p ./.gh-pages-cache
gh api "repos/${{ github.repository }}/actions/artifacts/$ARTIFACT_ID/zip" > vitest-reports.zip
unzip -q vitest-reports.zip -d ./.gh-pages-cache/vitest-reports
echo "✅ Downloaded and extracted Vitest reports"
else
echo "⚠️ Vitest reports not found, will generate from source"
fi
else
echo "⚠️ No successful Vitest runs found, will generate from source"
fi
else
echo "⚠️ Vitest workflow not found, will generate from source"
fi
<a href="./coverage/index.html" class="card">
<div class="card-icon">📊</div>
<h2 class="card-title">Test Coverage</h2>
<p class="card-description">Code coverage reports from Vitest unit tests</p>
<span class="card-status status-available">Available</span>
</a>
<a href="./vitest-ui/index.html" class="card">
<div class="card-icon">🧪</div>
<h2 class="card-title">Vitest Results</h2>
<p class="card-description">Interactive test results and reports</p>
<span class="card-status status-available">Available</span>
</a>
<a href="./knip/index.html" class="card">
<div class="card-icon">🔍</div>
<h2 class="card-title">Knip Report</h2>
<p class="card-description">Unused code and dependency analysis</p>
<span class="card-status status-available">Available</span>
</a>
</div>
<div class="footer">
<p>
Built from the <strong>main</strong> branch &bull;
<a href="https://github.com/Comfy-Org/ComfyUI_frontend" target="_blank">GitHub Repository</a> &bull;
<a href="https://docs.comfy.org" target="_blank">Official Documentation</a>
</p>
</div>
</div>
</body>
</html>
EOF
- name: Build static assets (with artifact reuse)
run: ./scripts/build-gh-pages.sh
- name: Setup Pages
uses: actions/configure-pages@v4
@@ -231,8 +120,21 @@ jobs:
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: './dist'
path: './docs/pages'
deploy-for-sno-deploy-ghpage-branch:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/sno-deploy-ghpage'
steps:
- name: Debug deployment
run: |
echo "Contents of ./docs/pages:"
ls -la ./docs/pages
echo "Contents of ./docs/pages/vitest-reports (if exists):"
ls -la ./docs/pages/vitest-reports || echo "No vitest-reports directory"
echo "Preview URL: https://comfyorg-comfyui-frontend.vercel.app"
deploy:
environment:
name: github-pages

View File

@@ -5,7 +5,7 @@ name: Storybook and Chromatic CI
on:
workflow_dispatch: # Allow manual triggering
pull_request:
branches: [main]
branches: [main, sno-deploy-ghpage]
jobs:
# Post starting comment for non-forked PRs

View File

@@ -46,3 +46,20 @@ jobs:
- name: Run Vitest tests
run: pnpm test:unit
- name: Generate test reports (on main branch)
if: github.ref == 'refs/heads/main'
run: |
mkdir -p ./vitest-reports
pnpm exec vitest \
--reporter=json --outputFile.json="./vitest-reports/results.json" \
--reporter=html --outputFile.html="./vitest-reports/index.html" \
--run
- name: Upload Vitest reports artifact (on main branch)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: vitest-reports
path: vitest-reports/
retention-days: 7

11
.gitignore vendored
View File

@@ -92,3 +92,14 @@ storybook-static
.github/instructions/nx.instructions.md
vite.config.*.timestamp*
vitest.config.*.timestamp*
# Github Pages Build output
/pages-dist
# Generated reports in docs/pages (exclude JSON data, keep HTML templates)
docs/pages/knip/report.json
docs/pages/vitest-ui/results.json
docs/pages/coverage/
docs/pages/nx-graph/
docs/pages/storybook/
docs/pages/playwright-reports/

14
docs/pages/.gitignore vendored Normal file
View File

@@ -0,0 +1,14 @@
*
!/.gitignore
!/README.md
!/index.html
!/knip/
!/knip/index.html
knip/report.md
!/vite.config.ts
!/tsconfig.json
/storybook/
/nx-graph/
/coverage/
/vitest-ui/
/playwright-reports/

View File

@@ -33,11 +33,7 @@ The deployment is managed by the `.github/workflows/deploy-gh-pages.yml` workflo
2. **Build Process**:
- Installs dependencies with pnpm
- Builds Storybook static site
- Generates Nx dependency graph
- Creates TypeDoc documentation
- Runs tests and generates coverage reports
- Generates Knip analysis report
- Runs `scripts/build-pages.sh` to generate Storybook, Nx dependency graph, Vitest reports, coverage, and Knip analysis
- Creates a landing page with links to all tools
3. **Deployment**:
@@ -49,7 +45,7 @@ The deployment is managed by the `.github/workflows/deploy-gh-pages.yml` workflo
### Build Steps
Each tool is built in its own step with `continue-on-error: true`, ensuring that if one tool fails to build, the others will still be deployed.
The build script handles optional tooling gracefully—if an individual tool fails to build, the remainder of the deployment still proceeds and the failure is logged as a warning.
#### Storybook (Required)
```bash
@@ -63,12 +59,12 @@ pnpm nx graph --file=dist/nx-graph/index.html
#### Test Coverage (Optional)
```bash
pnpm test:unit --run --coverage --coverage.reporter=html
pnpm exec vitest --run --coverage --coverage.reporter=html
```
#### Vitest Results (Optional)
```bash
pnpm test:unit --run --reporter=html
pnpm exec vitest --run --reporter=html --outputFile dist/vitest-ui/index.html
```
#### Knip Report (Optional)

215
docs/pages/index.html Normal file
View File

@@ -0,0 +1,215 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ComfyUI Frontend - Development Tools</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
/* bg: gray + white lines net */
background: #909090;
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.05) 2px, transparent 2px), radial-gradient(circle, rgba(255, 255, 255, 0.05) 2px, transparent 2px);
background-position: 0 0, 25px 25px;
background-size: 50px 50px;
}
.container {
max-width: 1200px;
width: 100%;
}
.header {
text-align: center;
color: white;
margin-bottom: 3rem;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
font-weight: 700;
}
.header p {
font-size: 1.125rem;
opacity: 0.9;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
p,h1,h2,h3,h4,h5,h6 {
transform: matrix(1, 0, -0.25, 1, 0, 0);
}
.card {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: transform 0.2s, box-shadow 0.2s;
text-decoration: none;
color: inherit;
display: block;
}
.card:hover {
transform: translateY(-4px);
box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15);
}
.card-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.card-title {
font-size: 1.5rem;
font-weight: 600;
margin-bottom: 0.5rem;
color: #1a202c;
}
.card-description {
color: #718096;
line-height: 1.6;
}
.card-status {
display: inline-block;
margin-top: 1rem;
padding: 0.25rem 0.75rem;
border-radius: 20px;
font-size: 0.875rem;
font-weight: 500;
}
.status-available {
background: #c6f6d5;
color: #22543d;
}
.status-loading {
background: #e9d8fd;
color: #44337a;
}
.status-unavailable {
background: #fed7d7;
color: #742a2a;
}
.footer {
text-align: center;
color: white;
margin-top: 3rem;
opacity: 0.8;
}
.footer a {
color: white;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎨 ComfyUI Frontend</h1>
<p>Development Tools & Documentation</p>
</div>
<div class="grid">
<a href="./storybook/index.html" class="card" data-status="storybook" data-fetch="./storybook/index.html">
<div class="card-icon">📚</div>
<h2 class="card-title">Storybook</h2>
<p class="card-description">Interactive component library and design system documentation</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
<a href="./nx-graph/index.html" class="card" data-status="nx-graph" data-fetch="./nx-graph/index.html">
<div class="card-icon">🔗</div>
<h2 class="card-title">Nx Dependency Graph</h2>
<p class="card-description">Visual representation of project dependencies and build structure</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
<a href="./coverage/index.html" class="card" data-status="coverage" data-fetch="./coverage/index.html">
<div class="card-icon">📊</div>
<h2 class="card-title">Test Coverage</h2>
<p class="card-description">Code coverage reports from Vitest unit tests</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
<a href="./playwright-reports/index.html" class="card" data-status="playwright" data-fetch="./playwright-reports/index.html">
<div class="card-icon">🎭</div>
<h2 class="card-title">Playwright E2E</h2>
<p class="card-description">Browser end-to-end test reports generated by Playwright</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
<a href="./vitest-ui/index.html" class="card" data-status="vitest-ui" data-fetch="./vitest-ui/index.html">
<div class="card-icon">🧪</div>
<h2 class="card-title">Vitest Results</h2>
<p class="card-description">Interactive test results and reports</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
<a href="./knip/index.html" class="card" data-status="knip" data-fetch="./knip/report.md">
<div class="card-icon">🔍</div>
<h2 class="card-title">Knip Report</h2>
<p class="card-description">Unused code and dependency analysis</p>
<span class="card-status status-loading" data-status-indicator>Checking…</span>
</a>
</div>
<div class="footer">
<p>
Built from the <strong>main</strong> branch &bull;
<a href="https://github.com/Comfy-Org/ComfyUI_frontend" target="_blank">GitHub Repository</a> &bull;
<a href="https://docs.comfy.org" target="_blank">Official Documentation</a>
</p>
</div>
</div>
<script>
const cards = Array.from(document.querySelectorAll('.card[data-status]'))
const setStatus = (card, variant, text) => {
const indicator = card.querySelector('[data-status-indicator]')
if (!indicator) return
indicator.classList.remove('status-loading', 'status-available', 'status-unavailable')
indicator.classList.add(`status-${variant}`)
indicator.textContent = text
}
const absoluteUrl = (href) => new URL(href, window.location.href).toString()
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), 10000)
cards.forEach((card) => {
const href = card.dataset.fetch || card.getAttribute('href')
if (!href) {
setStatus(card, 'unavailable', 'No link configured')
return
}
fetch(absoluteUrl(href), { cache: 'no-store', signal: controller.signal })
.then((response) => {
if (response.ok) {
setStatus(card, 'available', 'Available')
} else {
setStatus(card, 'unavailable', `Unavailable (${response.status})`)
}
})
.catch((error) => {
const reason = error.name === 'AbortError' ? 'Timed out' : 'Unavailable'
setStatus(card, 'unavailable', reason)
})
})
window.addEventListener('beforeunload', () => {
clearTimeout(timeout)
controller.abort()
})
</script>
</body>
</html>

9
docs/pages/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"composite": false
},
"include": [
"./vite.config.ts"
]
}

46
docs/pages/vite.config.ts Normal file
View File

@@ -0,0 +1,46 @@
import fs from 'node:fs'
import { resolve } from 'node:path'
import { defineConfig } from 'vite'
const rootDir = __dirname
const outDir = resolve(rootDir, '../pages-dist')
const discoverHtmlEntries = () => {
const entries = new Map<string, string>()
const topLevel = resolve(rootDir, 'index.html')
if (fs.existsSync(topLevel)) entries.set('index', topLevel)
for (const dirent of fs.readdirSync(rootDir, { withFileTypes: true })) {
if (!dirent.isDirectory() || dirent.name.startsWith('.')) continue
const candidate = resolve(rootDir, dirent.name, 'index.html')
if (fs.existsSync(candidate)) entries.set(dirent.name, candidate)
}
return entries.size > 0 ? Object.fromEntries(entries) : undefined
}
export default defineConfig({
root: rootDir,
base: '/ComfyUI_frontend',
appType: 'mpa',
logLevel: 'info',
publicDir: false,
server: {
open: '/index.html',
fs: {
allow: [rootDir],
strict: false
}
},
preview: {
open: '/index.html'
},
build: {
emptyOutDir: false,
outDir,
copyPublicDir: false,
rollupOptions: {
input: discoverHtmlEntries()
}
}
})

81
scripts/build-pages.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -Eeuo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
DIST="./docs/pages"
# Build or reuse Storybook
echo "[build-pages] Setting up Storybook"
rm -rf "$DIST/storybook"
if [ -d "./storybook-static" ] && [ "$(find ./storybook-static -name '*.html' | wc -l)" -gt 0 ]; then
echo "✅ Reusing downloaded Storybook build"
cp -r "./storybook-static" "$DIST/storybook"
elsew
echo "🔨 Building Storybook from source"
pnpm build-storybook && cp -r "storybook-static" "$DIST/storybook"
fi
echo "[build-pages] Generating Nx dependency graph"
rm -rf "$DIST/nx-graph" && mkdir -p "$DIST/nx-graph"
pnpm nx graph --file="$DIST/nx-graph/index.html"
# Generate or reuse Vitest test reports
echo "[build-pages] Setting up Vitest test reports"
rm -rf "$DIST/vitest-ui" && mkdir -p "$DIST/vitest-ui"
if [ -d "./.gh-pages-cache/vitest-reports" ]; then
echo "✅ Reusing downloaded Vitest reports"
cp -r "./.gh-pages-cache/vitest-reports/"* "$DIST/vitest-ui/" 2>/dev/null || echo "⚠️ No vitest reports to copy"
else
echo "🔨 Generating Vitest reports from source"
pnpm exec vitest \
--reporter=json --outputFile.json="$DIST/vitest-ui/results.json" \
--reporter=html --outputFile.html="$DIST/vitest-ui/index.html" \
--run
fi
# Set up Playwright test reports if available
echo "[build-pages] Setting up Playwright test reports"
if [ -d "./.gh-pages-cache/playwright-reports" ]; then
echo "✅ Reusing downloaded Playwright reports"
mkdir -p "$DIST/playwright-reports"
cp -r "./.gh-pages-cache/playwright-reports/"* "$DIST/playwright-reports/" 2>/dev/null || echo "⚠️ No playwright reports to copy"
fi
echo "[build-pages] Generating coverage report"
mkdir -p "$DIST/coverage"
if pnpm exec vitest --run --coverage --coverage.reporter=html --coverage.reportsDirectory="$DIST/coverage"; then
echo "✅ Coverage report completed"
else
echo "⚠️ Coverage report failed, continuing..."
fi
echo "[build-pages] Generating Knip report"
mkdir -p "$DIST/knip"
rm -f "$DIST/knip/report.md"
if pnpm knip --reporter markdown --no-progress --no-exit-code > "$DIST/knip/report.md" 2>/dev/null && [ -s "$DIST/knip/report.md" ]; then
echo "✅ Knip report generated at $DIST/knip/report.md"
else
echo "⚠️ Knip report failed, creating placeholder..."
cat > "$DIST/knip/report.md" <<'EOF'
# Knip report
> ⚠️ Knip report unavailable.
>
> Generation failed during build. See CI logs for details.
EOF
fi
if cp "${ROOT_DIR}/docs/pages/knip/index.html" "$DIST/knip/index.html" 2>/dev/null; then
echo "✅ Knip HTML wrapper completed"
else
echo "⚠️ Knip HTML wrapper missing, continuing..."
fi
echo "[build-pages] Landing page already exists at $DIST/index.html"
echo "[build-pages] Build artifacts ready in $DIST"
echo "[build-pages] Note: For local dev, you can develop the docs/pages/index.html using:
pnpm exec vite ${DIST}
"