Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4d29c6c
Scaffold VitePress: package.json, gitignore, minimal config
maxrjones Apr 28, 2026
3e7cac3
Add GitHub Actions workflow for VitePress + Pages deploy
maxrjones Apr 28, 2026
b0cb0f9
Remove Jekyll, Bundler, Pixi, Docker, RTD, slide framework, and redir…
maxrjones May 1, 2026
2d9ce6c
Move static assets to public/ for VitePress
maxrjones May 1, 2026
e27a6c6
Configure head: favicons, manifests, theme color, Google Analytics; d…
maxrjones May 1, 2026
5afe0a7
Theme: port existing brand link colors to VitePress brand vars
maxrjones May 1, 2026
3fbaed9
Redirects: build-time generator with YAML source; pin esbuild>=0.25.0…
maxrjones May 1, 2026
78c8fe0
Port home page to VitePress hero layout
maxrjones May 1, 2026
6e33e6e
Port community page to VitePress front-matter
maxrjones May 1, 2026
560fc3e
Port datasets page: strip Jekyll front-matter and font tags
maxrjones May 1, 2026
ac8fbc9
Port conventions page: convert font/ul to markdown bullets
maxrjones May 1, 2026
26c113e
Port implementations page: strip Jekyll front-matter and font tags
maxrjones May 1, 2026
9b8bbb5
Adopters: card grid layout with logo-only entries trailing
maxrjones May 1, 2026
4e35f7a
Port office-hours: rename dir, strip Jekyll front-matter, move timezo…
maxrjones May 1, 2026
b26461f
Port slides page: PDFs only, restored to public/slides/
maxrjones May 1, 2026
7338ef1
Configure nav, social links, edit-on-github, footer
maxrjones May 1, 2026
89e7000
Update README: VitePress dev/build instructions
maxrjones May 1, 2026
de18d91
Brand refresh: magenta palette, animated ECMWF hero with credits
maxrjones May 1, 2026
a375cff
Add Open Graph / Twitter card meta tags and sitemap; exclude NOTICE f…
maxrjones May 1, 2026
93e6ace
Add Read the Docs config for VitePress builds
maxrjones May 1, 2026
837e2c5
Read RTD canonical URL to set VitePress base path for sub-path deploys
maxrjones May 1, 2026
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
48 changes: 48 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
name: Deploy VitePress site to Pages

on:
push:
branches: [main]
workflow_dispatch:

permissions:
contents: read

concurrency:
group: pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
pages: write
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
fetch-depth: 0
persist-credentials: false
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: 20
cache: npm
- uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4
- run: npm ci
- run: npm run docs:build
- uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
path: .vitepress/dist

deploy:
needs: build
runs-on: ubuntu-latest
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- id: deployment
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4
8 changes: 3 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
_site
.sass-cache
.jekyll-metadata
node_modules/
.vitepress/dist/
.vitepress/cache/
.DS_Store
.swp
*~
26 changes: 12 additions & 14 deletions .readthedocs.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
# .readthedocs.yml
# Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# See https://docs.readthedocs.com/platform/stable/build-customization.html
# RTD configures the VitePress base URL automatically at build time.

# Required
version: 2

# Set the version of Python and other tools you might need
build:
os: ubuntu-24.04

os: ubuntu-lts-latest
tools:
ruby: "3.4"

commands:
- bundle install
- >
JEKYLL_ENV=production bundle exec jekyll build --destination
_readthedocs/html --baseurl $(echo -n "$READTHEDOCS_CANONICAL_URL" | cut
-d '/' -f 4-)
nodejs: "latest"
jobs:
install:
- npm ci
build:
html:
- npm run docs:build
- mkdir -p $READTHEDOCS_OUTPUT/
- mv .vitepress/dist $READTHEDOCS_OUTPUT/html
100 changes: 100 additions & 0 deletions .vitepress/config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { defineConfig } from 'vitepress'
import { redirectsPlugin, defaultPluginOptions } from './plugins/redirects'

// Read the path prefix from RTD's canonical URL env var when present
// (e.g. https://<slug>.readthedocs.build/en/171/ → /en/171/), so all
// asset URLs resolve correctly under the PR-preview subpath. Defaults
// to '/' for the GitHub Pages build at zarr.dev.
const rtdUrl = process.env.READTHEDOCS_CANONICAL_URL
const base = rtdUrl ? new URL(rtdUrl).pathname : '/'

export default defineConfig({
base,
title: 'Zarr',
description: 'Zarr is a community project to develop specifications and software for storage of large N-dimensional typed arrays, also commonly known as tensors.',
cleanUrls: true,
lastUpdated: true,
sitemap: { hostname: 'https://zarr.dev' },
head: [
['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }],
['link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: '/favicon-16x16.png' }],
['link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: '/favicon-32x32.png' }],
['link', { rel: 'apple-touch-icon', sizes: '180x180', href: '/apple-touch-icon.png' }],
['link', { rel: 'manifest', href: '/site.webmanifest' }],
['link', { rel: 'mask-icon', href: '/safari-pinned-tab.svg', color: '#5bbad5' }],
['meta', { name: 'apple-mobile-web-app-title', content: 'Zarr' }],
['meta', { name: 'application-name', content: 'Zarr' }],
['meta', { name: 'theme-color', content: '#ffffff' }],
['meta', { property: 'og:type', content: 'website' }],
['meta', { property: 'og:site_name', content: 'Zarr' }],
['meta', { property: 'og:title', content: 'Zarr' }],
['meta', { property: 'og:description', content: 'An open, community-driven format for storing large arrays in any key-value store, including cloud object storage.' }],
['meta', { property: 'og:image', content: 'https://zarr.dev/android-chrome-512x512.png' }],
['meta', { property: 'og:url', content: 'https://zarr.dev/' }],
['meta', { name: 'twitter:card', content: 'summary' }],
['meta', { name: 'twitter:title', content: 'Zarr' }],
['meta', { name: 'twitter:description', content: 'An open, community-driven format for storing large arrays in any key-value store, including cloud object storage.' }],
['meta', { name: 'twitter:image', content: 'https://zarr.dev/android-chrome-512x512.png' }],
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-BCRR9QE7Z0' }],
['script', {}, `window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-BCRR9QE7Z0');`],
],
srcExclude: [
'docs/**',
'README.md',
'NOTICE.md',
],
vite: {
plugins: [redirectsPlugin(defaultPluginOptions(process.cwd()))],
},
themeConfig: {
logo: '/android-chrome-192x192.png',
search: { provider: 'local' },
nav: [
{ text: 'Adopters', link: '/adopters' },
{ text: 'Community', link: '/community' },
{ text: 'Conventions', link: '/conventions' },
{ text: 'Datasets', link: '/datasets' },
{ text: 'Implementations', link: '/implementations' },
{ text: 'Office Hours', link: '/office-hours' },
{ text: 'Slides', link: '/slides' },
{
text: 'External',
items: [
{ text: 'Blog', link: 'https://zarr.dev/blog/' },
{ text: 'Specification', link: 'https://zarr-specs.readthedocs.io/' },
{ text: 'ZEPs', link: 'https://zarr.dev/zeps/' },
{ text: 'Documentation', link: 'https://zarr.readthedocs.io/en/stable/' },
],
},
],
socialLinks: [
{
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 600 530"><path d="M135.72 44.03C202.216 93.951 273.74 195.17 300 249.49c26.262-54.316 97.782-155.54 164.28-205.46C512.26 8.009 590-19.862 590 68.825c0 17.712-10.155 148.79-16.111 170.07-20.703 73.984-96.144 92.854-163.25 81.433 117.3 19.964 147.14 86.092 82.697 152.22-122.39 125.59-175.91-31.511-189.63-71.766-2.514-7.38-3.69-10.832-3.708-7.896-.017-2.936-1.193.516-3.707 7.896-13.714 40.255-67.233 197.36-189.63 71.766-64.444-66.128-34.605-132.26 82.697-152.22-67.108 11.421-142.55-7.45-163.25-81.433C20.15 217.612 9.997 86.535 9.997 68.825c0-88.687 77.742-60.816 125.72-24.795z" fill="currentColor"/></svg>',
},
link: 'https://bsky.app/profile/zarr.dev',
ariaLabel: 'Bluesky',
},
{ icon: 'mastodon', link: 'https://fosstodon.org/@zarr' },
{ icon: 'github', link: 'https://github.com/zarr-developers' },
{
icon: {
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.48 2 2 6.48 2 12c0 1.81.49 3.51 1.34 4.97L2 22l5.16-1.35A9.95 9.95 0 0 0 12 22c5.52 0 10-4.48 10-10S17.52 2 12 2z" fill="currentColor"/></svg>',
},
link: 'https://ossci.zulipchat.com/',
ariaLabel: 'Zulip',
},
],
editLink: {
pattern: 'https://github.com/zarr-developers/zarr-developers.github.io/edit/main/:path',
text: 'Edit this page on GitHub',
},
footer: {
message: 'Released under the MIT License.',
copyright: 'Copyright © Zarr contributors',
},
},
})
102 changes: 102 additions & 0 deletions .vitepress/plugins/redirects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { readFileSync, mkdirSync, writeFileSync, existsSync } from 'node:fs'
import { dirname, join, resolve } from 'node:path'
import yaml from 'js-yaml'
import type { Plugin } from 'vite'

interface RedirectEntry {
from: string
to: string
}

const escapeHtml = (s: string): string =>
s.replace(/&/g, '&amp;')
.replace(/"/g, '&quot;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')

const renderRedirectHtml = (to: string): string => {
const target = escapeHtml(to)
return `<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Redirecting...</title>
<link rel="canonical" href="${target}">
<meta http-equiv="refresh" content="0; url=${target}">
<meta name="robots" content="noindex">
</head>
<body>
<p>Redirecting to <a href="${target}">${target}</a>...</p>
<script>location.replace("${target}")</script>
</body>
</html>
`
}

const parseRedirectsYaml = (source: string): RedirectEntry[] => {
const parsed = yaml.load(source)
if (parsed === null || parsed === undefined) return []
if (typeof parsed !== 'object' || Array.isArray(parsed)) {
throw new Error('redirects.yml must be a YAML mapping at the top level')
}
const entries: RedirectEntry[] = []
for (const [from, to] of Object.entries(parsed as Record<string, unknown>)) {
if (typeof to !== 'string') {
throw new Error(`redirect target for "${from}" must be a string, got ${typeof to}`)
}
if (!from.startsWith('/')) {
throw new Error(`redirect source "${from}" must start with a leading slash`)
}
entries.push({ from, to })
}
return entries
}

const stripLeadingSlash = (p: string): string => (p.startsWith('/') ? p.slice(1) : p)
const stripTrailingSlash = (p: string): string => (p.endsWith('/') ? p.slice(0, -1) : p)

interface RedirectsPluginOptions {
yamlPath: string
outDir: string
}

export const redirectsPlugin = (options: RedirectsPluginOptions): Plugin => {
// VitePress emits both SSR and client bundles, so closeBundle fires twice.
// Only emit stubs on the first call.
let written = false
return {
name: 'zarr-redirects',
apply: 'build',
closeBundle() {
if (written) return
written = true

const yamlSource = readFileSync(options.yamlPath, 'utf8')
const entries = parseRedirectsYaml(yamlSource)

for (const { from, to } of entries) {
const cleaned = stripTrailingSlash(stripLeadingSlash(from))
const outPath = cleaned === ''
? join(options.outDir, 'index.html')
: join(options.outDir, cleaned, 'index.html')

if (existsSync(outPath)) {
throw new Error(
`Redirect "${from}" collides with an existing build output at ${outPath}. ` +
`Either remove the redirect or rename the page that produced ${outPath}.`,
)
}

mkdirSync(dirname(outPath), { recursive: true })
writeFileSync(outPath, renderRedirectHtml(to), 'utf8')
}

console.log(`[zarr-redirects] wrote ${entries.length} redirect stubs`)
},
}
}

export const defaultPluginOptions = (rootDir: string): RedirectsPluginOptions => ({
yamlPath: resolve(rootDir, '.vitepress/redirects.yml'),
outDir: resolve(rootDir, '.vitepress/dist'),
})
36 changes: 36 additions & 0 deletions .vitepress/redirects.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Each entry maps a path on zarr.dev to an absolute or relative target URL.
# A buildEnd hook turns this into static meta-refresh stub HTML files
# under .vitepress/dist/<from>/index.html.

# numcodecs codec docs (mirrors the deleted numcodecs_redirects/ verbatim)
/numcodecs/adler32: https://numcodecs.readthedocs.io/en/stable/checksum32.html#adler32
/numcodecs/astype: https://numcodecs.readthedocs.io/en/stable/astype.html
/numcodecs/bitround: https://numcodecs.readthedocs.io/en/stable/bitround.html
/numcodecs/blosc: https://numcodecs.readthedocs.io/en/stable/blosc.html
/numcodecs/bz2: https://numcodecs.readthedocs.io/en/stable/bz2.html
/numcodecs/crc32: https://numcodecs.readthedocs.io/en/stable/checksum32.html#crc32
/numcodecs/delta: https://numcodecs.readthedocs.io/en/stable/delta.html
/numcodecs/fixedscaleoffset: https://numcodecs.readthedocs.io/en/stable/fixedscaleoffset.html
/numcodecs/fletcher32: https://numcodecs.readthedocs.io/en/stable/checksum32.html#fletcher32
/numcodecs/gzip: https://numcodecs.readthedocs.io/en/stable/gzip.html
/numcodecs/jenkins_lookup3: https://numcodecs.readthedocs.io/en/stable/checksum32.html#jenkinslookup3
/numcodecs/lz4: https://numcodecs.readthedocs.io/en/stable/lz4.html
/numcodecs/lzma: https://numcodecs.readthedocs.io/en/stable/lzma.html
/numcodecs: https://numcodecs.readthedocs.io/en/stable/
/numcodecs/packbits: https://numcodecs.readthedocs.io/en/stable/packbits.html
/numcodecs/pcodec: https://numcodecs.readthedocs.io/en/stable/pcodec.html
/numcodecs/quantize: https://numcodecs.readthedocs.io/en/stable/quantize.html
/numcodecs/shuffle: https://numcodecs.readthedocs.io/en/stable/shuffle.html
/numcodecs/zfpy: https://numcodecs.readthedocs.io/en/stable/zfpy.html
/numcodecs/zlib: https://numcodecs.readthedocs.io/en/stable/zlib.html
/numcodecs/zstd: https://numcodecs.readthedocs.io/en/stable/zstd.html

# URL hygiene: catch any external link to /office_hours/
/office_hours/: /office-hours/

# Restored behavior of the deleted about/index.html stub
/about: /

# Vestigial 2019 blog posts -> blog repo
/2019/05/02/zarr-2.3-release/: https://zarr.dev/blog/release-23/
/2019/06/19/zarr-v3-update/: https://zarr.dev/blog/v3-update/
Loading