Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,22 @@ The documentation site uses [Astro](https://astro.build/) with [Starlight](https

This architecture keeps source docs clean and GitHub-friendly while providing a polished documentation site.

### Versioned Documentation Deployment

The documentation site supports multiple versions simultaneously. When working with releases:

- **Stable releases** (e.g., `v0.1.0`, `v0.2.0`) automatically deploy versioned docs
- **Documentation updates** can be made to old versions via `docs/v*` branches
- Users can switch between versions via the version switcher in the header

For detailed information on the versioned documentation system, including:
- How version deployment works (tags vs branches)
- Releasing new versions
- Updating existing version docs
- Managing the version list

See [docs/VERSIONED_DOCS.md](../docs/VERSIONED_DOCS.md).

### Writing Documentation

1. **Create a markdown file** in the appropriate directory:
Expand Down
187 changes: 172 additions & 15 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ name: Documentation

on:
push:
tags:
- 'v*'
branches:
- main
- 'docs/v*'
paths:
- 'docs/**'
- 'docs-site/**'
Expand All @@ -19,9 +22,16 @@ on:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write
contents: write

env:
# Site configuration - customize these for forks
# Production (dfinity/icp-cli):
PUBLIC_SITE: https://dfinity.github.io
PUBLIC_BASE_PREFIX: /icp-cli
# Forks should update these values:
# PUBLIC_SITE: https://your-username.github.io
# PUBLIC_BASE_PREFIX: /your-repo-name

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
Expand All @@ -30,10 +40,24 @@ concurrency:
cancel-in-progress: false

jobs:
# Build job
# Build job - validates that docs build successfully
build:
runs-on: ubuntu-latest
steps:
- name: Validate required environment variables
run: |
if [[ -z "${{ env.PUBLIC_SITE }}" ]]; then
echo "❌ Error: PUBLIC_SITE environment variable is not set"
exit 1
fi
if [[ -z "${{ env.PUBLIC_BASE_PREFIX }}" ]]; then
echo "❌ Error: PUBLIC_BASE_PREFIX environment variable is not set"
exit 1
fi
echo "✅ Required environment variables are set:"
echo " PUBLIC_SITE=${{ env.PUBLIC_SITE }}"
echo " PUBLIC_BASE_PREFIX=${{ env.PUBLIC_BASE_PREFIX }}"

- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

Expand All @@ -53,21 +77,154 @@ jobs:
run: npm run build
env:
NODE_ENV: production
PUBLIC_SITE: ${{ env.PUBLIC_SITE }}
PUBLIC_BASE_PREFIX: ${{ env.PUBLIC_BASE_PREFIX }}

# Publish root index and versions list - only runs on main branch
publish-root-files:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

- name: Upload artifact
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
- name: Setup Node
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v4.2.0
with:
path: ./docs-site/dist
node-version: '20'

- name: Prepare root files
run: |
mkdir -p root

# Copy versions.json from repo
cp docs-site/versions.json root/versions.json

# Extract the latest version from versions.json
LATEST_VERSION=$(jq -r ".versions[] | select(.latest == true) | .version" docs-site/versions.json)

# If no releases yet, redirect to main branch docs
if [[ -z "$LATEST_VERSION" ]]; then
echo "⚠️ No releases yet, redirecting to main branch docs"
LATEST_VERSION="main"
else
echo "✅ Redirecting to version: ${LATEST_VERSION}"
fi

# Generate index.html that redirects to latest version
cat > root/index.html << EOF
<!doctype html>
<html>
<head>
<meta http-equiv="refresh" content="0; url=./${LATEST_VERSION}/" />
<meta name="robots" content="noindex" />
</head>
</html>
EOF

# Deployment job - only runs on main branch
deploy:
- name: Deploy root files to GitHub Pages
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./root
keep_files: true

# Publish main branch docs for preview (always available at /main/)
publish-main-docs:
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
needs: build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

- name: Setup Node
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v4.2.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: docs-site/package-lock.json

- name: Install dependencies
working-directory: ./docs-site
run: npm ci

- name: Build main branch docs
working-directory: ./docs-site
run: |
BASE_PREFIX="${PUBLIC_BASE_PREFIX:-/icp-cli}"
echo "Building with base: ${BASE_PREFIX}/main/"
npm run build
env:
NODE_ENV: production
PUBLIC_BASE_PATH: ${{ env.PUBLIC_BASE_PREFIX }}/main/
PUBLIC_SITE: ${{ env.PUBLIC_SITE }}

- name: Deploy main docs to GitHub Pages
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs-site/dist
destination_dir: main
keep_files: true

# Publish versioned docs - runs on tags (v*) or docs branches (docs/v*)
publish-versioned-docs:
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/v') || startsWith(github.ref, 'refs/heads/docs/v'))
needs: build
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
- name: Checkout
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0

- name: Setup Node
uses: actions/setup-node@1a4442cacd436585916779262731d5b162bc6ec7 # v4.2.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: docs-site/package-lock.json

- name: Install dependencies
working-directory: ./docs-site
run: npm ci

- name: Extract version from tag or branch
run: |
# Use repo-specific base prefix from env vars (defaults to /icp-cli)
BASE_PREFIX="${PUBLIC_BASE_PREFIX:-/icp-cli}"

if [[ "${GITHUB_REF}" == refs/tags/v* ]]; then
# Tag: v0.1.0 -> extract major.minor -> 0.1
VERSION=${GITHUB_REF#refs/tags/v}
# Strip patch version (0.1.0 -> 0.1)
VERSION=${VERSION%.*}
echo "DOCS_VERSION=${VERSION}" >> $GITHUB_ENV
echo "DOCS_BASE_PATH=${BASE_PREFIX}/${VERSION}/" >> $GITHUB_ENV
elif [[ "${GITHUB_REF}" == refs/heads/docs/v* ]]; then
# Branch: docs/v0.1 -> extract version -> 0.1
VERSION=${GITHUB_REF#refs/heads/docs/v}
echo "DOCS_VERSION=${VERSION}" >> $GITHUB_ENV
echo "DOCS_BASE_PATH=${BASE_PREFIX}/${VERSION}/" >> $GITHUB_ENV
else
echo "❌ Docs should only be published for version tags (v*) or docs branches (docs/v*)"
echo "Current ref: ${GITHUB_REF}"
exit 1
fi

echo "Building docs for version ${VERSION} with base: ${BASE_PREFIX}/${VERSION}/"

- name: Build documentation site
working-directory: ./docs-site
run: npm run build
env:
NODE_ENV: production
PUBLIC_BASE_PATH: ${{ env.DOCS_BASE_PATH }}
PUBLIC_SITE: ${{ env.PUBLIC_SITE }}

- name: Deploy versioned docs to GitHub Pages
uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4.0.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./docs-site/dist
destination_dir: ${{ env.DOCS_VERSION }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ icp-cli-network-launcher
# Documentation build artifacts
docs-site/.astro/
docs-site/dist/
docs-site/dist-test/
docs-site/node_modules/
docs-site/.docs-temp/
11 changes: 9 additions & 2 deletions docs-site/astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ import rehypeExternalLinks from 'rehype-external-links';

// https://astro.build/config
export default defineConfig({
site: 'https://dfinity.github.io',
base: process.env.NODE_ENV === 'production' ? '/icp-cli/' : '/',
site: process.env.PUBLIC_SITE,
// For versioned deployments: /icp-cli/0.1/, /icp-cli/0.2/, etc.
// For non-versioned: /icp-cli/ in production, / in development
// Defaults are set in the workflow, not here
base: process.env.PUBLIC_BASE_PATH || (process.env.NODE_ENV === 'production' ? process.env.PUBLIC_BASE_PREFIX + '/' : '/'),
markdown: {
rehypePlugins: [
// Open external links in new tab
Expand All @@ -17,6 +20,9 @@ export default defineConfig({
title: 'ICP CLI',
description: 'Command-line tool for developing and deploying applications on the Internet Computer Protocol (ICP)',
favicon: '/favicon.png',
components: {
SiteTitle: './src/components/SiteTitle.astro',
},
head: [
{
tag: 'script',
Expand All @@ -38,6 +44,7 @@ export default defineConfig({
logo: {
src: './src/assets/icp-logo.svg',
replacesTitle: false,
alt: 'ICP',
},
customCss: [
'./src/styles/layers.css',
Expand Down
73 changes: 73 additions & 0 deletions docs-site/src/components/SiteTitle.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
import type { Props } from '@astrojs/starlight/props';
import config from 'virtual:starlight/user-config';
import VersionSwitcher from './VersionSwitcher.astro';
import icpLogo from '../assets/icp-logo.svg';

// Ensure title is a string
const titleText = typeof config.title === 'string' ? config.title : 'ICP CLI';

// Extract logo info safely, but use imported asset for src
const logo = config.logo as any;
const hasLightLogo = logo && 'src' in logo;
const hasDarkLogo = logo && 'dark' in logo && typeof logo.dark === 'object' && logo.dark && 'src' in logo.dark;

// Use imported logo asset instead of config path
const logoSrc = icpLogo.src;
---

<div class="site-title-wrapper sl-flex">
<a href={import.meta.env.BASE_URL} class="site-title sl-flex">
{
hasLightLogo ? (
<img
class:list={{ 'light:sl-hidden': hasDarkLogo, logo: true }}
alt={logo.alt ?? 'Logo'}
src={logoSrc}
width={logo.width}
height={logo.height}
/>
) : null
}
{
hasDarkLogo ? (
<img
class:list={{ 'dark:sl-hidden': true, logo: true }}
alt={logo.dark.alt ?? logo.alt ?? 'Logo'}
src={logoSrc}
width={logo.dark.width ?? logo.width}
height={logo.dark.height ?? logo.height}
/>
) : null
}
<span class:list={{ 'sr-only': logo?.replacesTitle }}>
{titleText}
</span>
</a>
<VersionSwitcher />
</div>

<style>
.site-title-wrapper {
align-items: center;
gap: var(--sl-nav-gap);
}

.site-title {
align-items: center;
gap: var(--sl-nav-gap);
font-size: var(--sl-text-h4);
font-weight: 600;
color: var(--sl-color-text-accent);
text-decoration: none;
white-space: nowrap;
}

.logo {
height: calc(var(--sl-nav-height) - 2 * var(--sl-nav-pad-y));
width: auto;
max-width: 100%;
object-fit: contain;
object-position: 0 50%;
}
</style>
Loading
Loading