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
26 changes: 16 additions & 10 deletions .github/workflows/build_deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,34 @@ jobs:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Ruby
uses: ruby/setup-ruby@v1
- name: Setup Node
uses: actions/setup-node@v4
with:
ruby-version: '3.3'
bundler-cache: true
node-version: '22'
cache: npm

- name: Install dependencies
run: npm ci

- name: Fetch software documentation sources
run: npm run fetch-sources

- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
with:
enablement: true

- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: bundle exec jekyll build --verbose --trace --baseurl "${{ steps.pages.outputs.base_path }}"
- name: Build with Astro
# Outputs to the './dist' directory; postbuild runs Pagefind indexing
run: npm run build
env:
JEKYLL_ENV: production
JEKYLL_LOG_LEVEL: debug
CI: true

- name: Upload artifact
# Automatically uploads an artifact from the './_site' directory by default
uses: actions/upload-pages-artifact@v4
with:
path: ./dist

# Deployment job
deploy:
Expand Down
34 changes: 13 additions & 21 deletions .github/workflows/links.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,34 +12,26 @@ jobs:
steps:
- uses: actions/checkout@v4

- uses: ruby/setup-ruby@v1
- uses: actions/setup-node@v4
with:
ruby-version: '3.3'
bundler-cache: true
node-version: '22'
cache: npm

- name: Setup Pages
id: pages
uses: actions/configure-pages@v5
with:
enablement: true
- name: Install dependencies
run: npm ci

- name: Build with Jekyll
# Outputs to the './_site' directory by default
run: bundle exec jekyll build --verbose --trace --baseurl "${{ steps.pages.outputs.base_path }}"
- name: Build with Astro
# prebuild clones the software docs sources into vendor/;
# postbuild runs Pagefind indexing. Output: './dist'.
run: npm run build
env:
JEKYLL_ENV: production
CI: true

- name: Link Checker
id: lychee
uses: lycheeverse/lychee-action@v2
with:
args: --base _site/ --verbose --no-progress '_site/**/*.html'
# --root-dir resolves root-relative links against the local build;
# lychee.toml holds accept codes and exclusions (self, bot-blocked sites).
args: --root-dir ${{ github.workspace }}/dist --config lychee.toml --no-progress 'dist/**/*.html'
fail: true

# - name: Create Issue From File
# if: steps.lychee.outputs.exit_code != 0
# uses: peter-evans/create-issue-from-file@v5
# with:
# title: Link Checker Report
# content-filepath: ./lychee/out.md
# labels: report, automated issue
28 changes: 28 additions & 0 deletions .github/workflows/links_live.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: links_live

# Post-deploy link check of the LIVE site: verifies that production URLs —
# including the absolute canonical/OG URLs that pre-deploy checks must skip —
# actually resolve. Runs after every successful deploy and weekly.

on:
workflow_run:
workflows: [build_deploy]
types: [completed]
branches: [main]
schedule:
- cron: '23 4 * * 1' # Mondays 04:23 UTC
workflow_dispatch:

jobs:
link_checker_live:
if: ${{ github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success' }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Link Checker (live site)
id: lychee
uses: lycheeverse/lychee-action@v2
with:
args: --config lychee-live.toml --no-progress 'https://www.rnpgp.org'
fail: true
Comment on lines +18 to +28
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@ _site/
.jekyll-cache/
.jekyll-metadata
.DS_Store
Gemfile.lock
Gemfile.lock
# Astro / Node
node_modules/
dist/
.astro/
# Pulled-in software sources (fetch-sources.mjs)
vendor/
24 changes: 0 additions & 24 deletions 404.html

This file was deleted.

156 changes: 156 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# AGENTS.md

Guidance for AI coding agents working on this repository.

## Project overview

This is the source of the RNP project website, <https://www.rnpgp.org> — a
static site built with **Astro 7** (Node ≥ 22.12 required), styled with
**Tailwind CSS 4** (via `@tailwindcss/vite`, Vite 8 / Rolldown) and made
interactive with **Vue 3** islands (`@astrojs/vue`). RNP itself (the C++
OpenPGP/LibrePGP library) lives in a separate repository
([rnpgp/rnp](https://github.com/rnpgp/rnp)); this repository contains only the
website.

- Content is **AsciiDoc** (blog posts, advisories — rendered at build time by
Asciidoctor.js via a custom content loader) and **Markdown** (software,
specs). Write new content in English.
- Design system: "The Cryptographer's Paper" — IBM Plex (Plex Sans for
text/display, Plex Mono for code/fingerprints/labels, self-hosted in
`public/fonts/`), light-first pastel look with full dark mode, brand colors
blue `#1A7BEC` / teal `#00DFB7` / gold `#FFDC4A` / navy `#14172B`, and the
blue→teal brand gradient hero.
- Deployed to GitHub Pages by GitHub Actions (see "CI" below).

## Repository layout

- `astro.config.mjs` — Astro config (site URL, vue, sitemap, Tailwind Vite
plugin, Shiki dual themes).
- `src/content.config.ts` — content collections: `blog`, `advisories`
(AsciiDoc via custom loader), `software`, `specs` (Markdown glob loaders),
`softwareDocs`, `manpages` (AsciiDoc over `vendor/`). Also defines
`RNP_VERSION` for man pages.
- `src/lib/asciidoc.ts` — Asciidoctor.js wrapper + the custom content-layer
loader (`adocLoader`) that renders `.adoc` → HTML and stores it as
`rendered.html`, so entries work with `render(entry)` / `<Content />`.
- `src/lib/github.ts` — build-time "latest release" fetch with pinned fallback.
- `src/lib/tags.ts` — namespaced tag labels (`writtenin:C++` → "Written in C++").
- `src/content/blog/*.adoc` — blog posts, one `YYYY-MM-DD-slug.adoc` per post.
URL: `/blog/<id>/` (id = filename). Front matter: `title`, `date`,
`categories`, `authors` (`name`/`email`/`social_links`), `excerpt`,
optional `redirect_from` (legacy `/blog/DD-MM-YYYY/.../` URLs are emitted as
redirect pages).
- `src/content/advisories/*.adoc` — security advisories. Front matter:
`title`, `date`, `id` (e.g. `RI-2021-01`), `cve_id`, `excerpt`.
- `src/content/software/*.md` — product pages. Front matter: `title`,
`description`, `repo_url`, `tags`, `external_links` (`{url, title}`),
optional `docs_repo`/`docs_subtree`/`docs_ref` (enables on-site docs),
optional `feature_with_priority` (home-page featuring).
- `src/content/specs/*.md` — IETF draft specs (full text in a fenced block).
- `src/pages/` — routes, incl. `rss.xml.ts`, `robots.txt.ts`, `404.astro`.
- `src/layouts/` — `BaseLayout.astro` (shell: header, footer, SEO/OG, theme
script, ClientRouter), `DocsLayout.astro` (product docs with sidebar).
- `src/components/` — Astro components; `src/components/vue/` — hydrated Vue
islands (`SiteHeader`, `SiteSearch`, `HeroDecrypt`,
`FingerprintPlayground`, `InstallTabs`, `BlogBrowser`,
`KeyFingerprintCards`, `ThemeToggle`, `Reveal`, `CopyButton`).
- `src/styles/global.css` — Tailwind import, `@theme` tokens, semantic
light/dark CSS variables, prose/asciidoctor skin, Shiki dual-theme CSS.
- `public/` — fonts, brand SVGs, `openpgp_keys/*.asc`, favicons, `og.png`.
- `scripts/fetch-sources.mjs` — sparse-clones software repos into `vendor/`.
- `vendor/` — pulled-in repos (gitignored; delete to force a refresh).

## Pulled-in content (do not commit)

`fetch-sources.mjs` (auto-run by `predev`/`prebuild`) sparse-clones each
software entry's `docs_repo` into `vendor/<name>/` (blob-less, depth 1, pinned
to `docs_ref` when set) and checks out only `README*`, the `docs_subtree`
directory, and — for rnp — the man-page sources
(`src/rnp/rnp.1.adoc`, `src/rnpkeys/rnpkeys.1.adoc`, `src/lib/librnp.3.adoc`).
The `softwareDocs` and `manpages` collections render from `vendor/`; if it is
missing (e.g. offline), those collections are empty and the build continues
with a warning. Existing clones are reused — `rm -rf vendor` to refresh.

**To update man pages / embedded docs after a new RNP release:** bump
`docs_ref` in `src/content/software/rnp.md`, `RNP_VERSION` in
`src/content.config.ts`, the fallback in `src/lib/github.ts`, then
`rm -rf vendor/rnp && npm run build`.

## Build and test commands

- `npm install` — install dependencies.
- `npm run dev` — dev server (runs fetch-sources first).
- `npm run build` — production build to `dist/` (fetch-sources + Pagefind).
- `npm run preview` — serve the production build locally.
- `npm run fetch-sources` — refresh `vendor/` manually.
- `npm run check:links` — verify every internal href/src in `dist/` exists.
- `npm run check:links:external` — full lychee check (internal + external) per
`lychee.toml` (accept 429; excludes our own production URLs and bot-blocked
sites). Must pass before merging; CI runs the same check.
- `npm run test:e2e` — interactive end-to-end checks (drives headless Chrome
via CDP against a running preview on :4325; macOS Chrome required).

There is no unit test suite; validation is building + link checking.

## Conventions

- **Styling**: use the semantic Tailwind utilities (they auto-adapt to dark
mode): `bg-background`, `bg-surface`, `bg-surface-dim`, `text-foreground`,
`text-muted`, `text-faint`, `border-line`, `text-accent`, `text-accent2`.
Do **not** write `dark:` variants for them. Custom classes from global.css:
`.mono-label`, `.card`, `.card-hover`, `.gradient-rule`, `.graph-bg`,
`.btn`, `.btn-primary`, `.btn-ghost`, `.fingerprint`.
- **Imports**: use the `@/` alias (`@/components/...`, `@/layouts/...`).
- **Islands**: interactivity goes in Vue SFCs under `src/components/vue/`,
hydrated with `client:visible` (below the fold) or `client:load`; props must
be JSON-serializable. Respect `prefers-reduced-motion`.
- **Search**: Pagefind index is generated post-build (`pagefind --site dist`);
the `SiteSearch` island loads `/pagefind/pagefind.js` at runtime (absent in
plain `astro dev`).
- **Dead links**: when an external link dies, point it at a Wayback Machine
snapshot (`https://archive.org/wayback/available?url=<url>&timestamp=<yyyymmdd>`
finds the closest capture) instead of a substitute page — the snapshot
preserves exactly what the content referenced. Upstream/vendor links are
fixed via `UPSTREAM_LINK_FIXES` in `src/content.config.ts`.
- **Astro 7 notes**: `src/content.config.ts` (not `src/content/config.ts`);
`z` from `astro/zod`; `entry.id` + `render(entry)` (no `slug`/
`entry.render()`); Asciidoctor.js 4.x is async (`await load()`,
`await doc.convert()`); whitespace between adjacent inline elements in
`.astro` collapses JSX-style — use `{' '}`.

## CI

- `.github/workflows/build_deploy.yml` — Node 22, `npm ci`, `fetch-sources`,
build, deploy `dist/` to GitHub Pages (deploy only from `main`).
- `.github/workflows/links.yml` — builds the site and runs lychee over
`dist/**/*.html` pre-deploy (config: `lychee.toml`); fails on broken links.
- `.github/workflows/links_live.yml` — post-deploy lychee crawl of the live
site (`lychee-live.toml`, which does not exclude www.rnpgp.org) after each
successful deploy + weekly; this is where the absolute canonical/OG URLs
get verified in production.
- `.github/workflows/codeql.yml` — CodeQL analysis.

## Gotchas

- `Gemfile.lock`, `_site/`, `vendor/`, `dist/`, `node_modules/` are gitignored.
`package-lock.json` **is** committed — keep it in sync.
- Old blog URLs (`/blog/DD-MM-YYYY/slug/`) are redirect pages generated from
posts' `redirect_from` front matter — keep that field when touching old posts.
- The `openpgp_keys/*.asc` files are release-signing keys; treat them as
immutable unless a key actually rotates.
- Man-page/description strings in `src/content.config.ts` are parsed from the
AsciiDoc `== NAME` sections — if upstream renames sections, descriptions
fall back to empty.
- `parent-hub/` is a legacy nested checkout consumed by open.ribose.com;
leave it untouched.
- **Tailwind v4 important syntax**: the important modifier is a *suffix*
(`border-teal!`), not the v3 prefix (`!border-teal`). You rarely need it —
custom classes in `@layer components` (`.mono-label`, `.btn`, …) lose to
plain utilities anyway.
- **Teleport + SSR**: never render `<Teleport>` unconditionally in an
SSR-hydrated island — guard it (`v-if="mounted"`, set in `onMounted`) or the
hydration tree mismatches ("rendered on server: script, expected:
Symbol(v-cmt)").
- **Runtime-only URLs**: dynamic `import('/pagefind/pagefind.js')` breaks
dev/SSR transforms even with `@vite-ignore`; use
`new Function('u', 'return import(u)')` so Vite never sees the specifier.
20 changes: 0 additions & 20 deletions Gemfile

This file was deleted.

33 changes: 0 additions & 33 deletions Makefile

This file was deleted.

Loading
Loading