The UC OSPO Network website, built with MyST.
- Node.js and npm. Use the version in
.nvmrc(runnvm use); Node 24.17.0+ currently breaks the accessibility check due to an upstream regression. - Python 3.10+ (optional, for pre-commit hooks)
git clone git@github.com:UC-OSPO-Network/ucospo.net.git
cd ucospo.netnpm installPre-commit hooks run Prettier, markdownlint, Black, and codespell automatically before each commit. To enable them, first create a Python virtual environment and install the hook:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
pre-commit installIf you skip this step, the pre-commit.ci bot will check your PR automatically instead.
make serve
# or: npx myst startThis starts a live-reload dev server, typically at http://localhost:3000.
make html
# or: npx myst build --htmlOutput goes to _build/html/.
myst.yml # site config and table of contents
index.md # home page
about.md # about page
news.md # news page
campus/ # per-campus pages
events/ # event pages
guiding-themes/ # guiding themes section
oss-resources/ # open source resource guides
posts/ # blog posts
static/ # images, CSS, and other static assets
- Pages are MyST Markdown.
- The table of contents is defined in
myst.ymlunderproject.toc. Add new pages there as well as creating the.mdfile. - Blog post frontmatter uses
tagsas an array:tags: [tag1, tag2]. - Admonitions use MyST syntax:
:::{note},:::{tip},:::{important}.
Blog posts live in posts/. Each post is a folder containing an index.md plus any images it uses. Adding a post involves creating the post itself and then registering it in a few places so it appears in the site's navigation, blog index, and news feed.
Make a folder named with the post's URL slug and add an index.md:
posts/my-post-slug/index.md
Start it with frontmatter:
---
date: 2026-06-16
title: "Your Post Title"
description: "One-sentence summary used on the blog card and in the Atom feed."
author: Author Name
tags: [tag1, tag2] # optional
----
date(required) drives the newest-first ordering everywhere. UseYYYY-MM-DD. -
titleanddescription(required) are reused on the blog index card and in the Atom feed. -
For multiple authors, use a list instead of
author:authors: - First Author - Second Author
-
Put images in the same folder as the post and reference them by filename (e.g.
or a:::{figure}directive). Include descriptive alt text, which both markdownlint and pa11y-ci check for.
Update these three files so the post is linked from the rest of the site:
| File | What to add |
|---|---|
posts/index.md |
A :::{card} block linking to /posts/my-post-slug, newest-first. |
myst.yml |
A - file: posts/my-post-slug/index.md entry under the Blog section, newest-first. |
news.md |
A short heading and a line linking to the post (only if it's newsworthy). |
Copy an existing entry in each file and adjust the title, link, and date.
You do not need to touch these; they regenerate from post frontmatter at build time:
- Atom feed (
scripts/generate-feed.js, run duringmake html): picks up anyposts/*/index.mdthat has adateandtitle. - Sitemap: MyST generates
_build/html/sitemap.xmlon build.
Run make serve and confirm the post renders, appears on the blog index, and is linked from the sidebar nav. make html followed by the link and accessibility checks (see below) catches broken links and missing alt text before you open a PR.
This project uses pre-commit with:
- Prettier — auto-formats Markdown, CSS, and YAML
- markdownlint — checks Markdown structure and accessibility (heading levels, alt text, informative link text, etc.). Configuration is in
.markdownlint-cli2.yaml. - Black — Python code formatting
- codespell — catches common typos
If you push without running pre-commit locally, the pre-commit.ci bot will check your PR automatically. See CONTRIBUTING.md for usage details.
The site's production build is deployed to GitHub Pages via GitHub Actions.
On push to main: .github/workflows/deploy.yml builds the site with make html and publishes it to the UC-OSPO-Network.github.io repo (also on a 12-hour schedule, to keep the aggregated event feeds current).
On pull requests: .github/workflows/pr.yml runs the build and checks (linkinator + pa11y-ci). Branch PRs also get a Netlify deploy preview (built from netlify.toml, linked from the PR's checks); fork PRs get the build check but not the preview. The Netlify preview is not production.
CI checks that run on every PR:
| Check | Tool | What it catches |
|---|---|---|
| Build | MyST | Broken Markdown, missing files, config errors |
| Links | linkinator | Broken internal and external links |
| Accessibility | pa11y-ci | WCAG 2.1 AA violations (contrast, alt text, etc.) |
| Linting/formatting | pre-commit.ci | Formatting, Markdown structure, typos |
See CONTRIBUTING.md for the full contribution workflow including forking, branching, and the PR review process.