Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
9ec7809
Update the deploy workflow actions
DZPM Sep 29, 2026
5bf16d1
Remove bin/publish
DZPM Sep 29, 2026
7e4394c
Stop tracking Hugo build artifacts
DZPM Sep 29, 2026
1efbd59
Remove the dead Google Analytics property from the site config
DZPM Sep 29, 2026
cb3a62f
Enable git info and robots.txt generation
DZPM Sep 29, 2026
9a1dfee
Add an Allow rule to the robots.txt template
DZPM Sep 29, 2026
ab6c5c9
Fix the favicon URL and add an ico, apple-touch-icon and theme-color
DZPM Sep 29, 2026
d8c0e34
Replace the dead Meetup JSONP list with a static link
DZPM Sep 29, 2026
416b583
Make the home banner follow the newest PyDay
DZPM Sep 29, 2026
c52a859
Remove the Wikimedia hotlink from the 404 page and improve the copy
DZPM Sep 29, 2026
649c6f0
Remove third-party requests from the archived snapshots
DZPM Sep 29, 2026
4ed2410
Self-host Font Awesome 5.8.2
DZPM Sep 29, 2026
c610b60
Add humans.txt, security.txt and llms.txt
DZPM Sep 29, 2026
8e8bca3
Merge branch 'chore/remove-dead-analytics' into work/plumbing
DZPM Oct 1, 2026
a70b611
Add checks on pull requests
DZPM Sep 29, 2026
ae6b21c
Merge branch 'work/plumbing' into pr/2-checks
DZPM Oct 3, 2026
e901204
Merge branch 'ci/pr-checks' into pr/2-checks
DZPM Oct 3, 2026
9d2bec1
Merge branch 'ci/update-actions' into pr/2-checks
DZPM Oct 3, 2026
c15e0bf
Merge branch 'chore/retire-bin-publish' into pr/2-checks
DZPM Oct 3, 2026
faa0edc
Merge branch 'chore/untrack-build-artifacts' into pr/2-checks
DZPM Oct 3, 2026
55f1a6f
ci: pin every action to a commit SHA
DZPM Oct 3, 2026
e04e76c
fix(config): disable the unused tags and categories taxonomies
DZPM Oct 3, 2026
7ccba6f
ci: run the pull request checks on every pull request
DZPM Oct 4, 2026
0401cb5
security.txt: remove the advisory form from the contacts
DZPM Oct 4, 2026
2cda294
ci: add Dependabot for the pinned actions
DZPM Oct 4, 2026
5bb707b
README: list the real prerequisites
DZPM Oct 4, 2026
41ff97f
ci: check the links against the build, not against the live site
DZPM Oct 4, 2026
9810c12
ci: read the Hugo version from .hugo-version
DZPM Oct 4, 2026
192fe15
Merge branch 'pr/1-fixes' into pr/2-checks
DZPM Oct 4, 2026
3392466
ci: exclude the Slack workspace from the link check
DZPM Oct 4, 2026
f06b1ef
chore(theme): remove the dead npm manifest
DZPM Oct 4, 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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Keep the SHA-pinned actions in .github/workflows current. Dependabot updates
# the commit SHA and the version comment next to it together.
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
commit-message:
prefix: ci
17 changes: 11 additions & 6 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,31 @@ on:
branches:
- edition

permissions:
contents: write

jobs:
deploy:
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
submodules: true # Fetch Hugo themes (true OR recursive)
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod

- name: Read the Hugo version
run: echo "HUGO_VERSION=$(tr -d '[:space:]' < .hugo-version)" >> "$GITHUB_ENV"

- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1
with:
hugo-version: '0.139.4'
hugo-version: ${{ env.HUGO_VERSION }}
extended: true

- name: Build
run: hugo --minify

- name: Deploy
uses: peaceiris/actions-gh-pages@v3
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4.1.0
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_branch: master
Expand Down
87 changes: 87 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
name: pr-checks

on:
pull_request:

permissions:
contents: read

jobs:
build:
name: Build the site
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0 # Fetch all history for .GitInfo and .Lastmod

- name: Read the Hugo version
run: echo "HUGO_VERSION=$(tr -d '[:space:]' < .hugo-version)" >> "$GITHUB_ENV"

- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true

- name: Build
run: hugo --minify

content:
name: Check the content
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'

- name: Install dependencies
run: pip install pyyaml

- name: Check front matter and cross-references
run: bin/check-content

links:
name: Check the links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Read the Hugo version
run: echo "HUGO_VERSION=$(tr -d '[:space:]' < .hugo-version)" >> "$GITHUB_ENV"

- name: Setup Hugo
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1
with:
hugo-version: ${{ env.HUGO_VERSION }}
extended: true

- name: Build
# A root-relative baseURL turns every internal link into "/path/", so
# lychee resolves it inside this build and not on the live site.
run: hugo --minify -d public --baseURL /

- name: Check links
uses: lycheeverse/lychee-action@e7477775783ea5526144ba13e8db5eec57747ce8 # v2.9.0
with:
# --root-dir: where "/path/" links resolve. Without it lychee flags
# every absolute link in a local file as an error.
# --exclude-path: the frozen 2016-2019 snapshots under archives/
# keep their dead links on purpose.
# --exclude: hosts that answer bots with 400, 403, or 999.
args: >-
--no-progress
--exclude-all-private
--max-concurrency 4
--root-dir "$GITHUB_WORKSPACE/public"
--exclude-path 'public/archives/'
--exclude 'twitter\.com'
--exclude '^https?://(www\.)?x\.com/'
--exclude 'linkedin\.com'
--exclude 'pybcn\.slack\.com'
'public/**/*.html'
# External sites rate-limit. Keep the report in the job summary and
# do not turn the check red.
fail: false
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
.idea
bin/hugo
public/**

# Hugo build output and caches
docs/
resources/
.hugo_build.lock
Empty file removed .hugo_build.lock
Empty file.
2 changes: 1 addition & 1 deletion README.md
Comment thread
ber2 marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ deployed using [Hugo](https://gohugo.io).
## Usage for content publishing

## Prerequisites
- pip
- `curl`, `tar`, and `sha256sum`. The `bin/install` script needs them to download and verify the Hugo binary.

### Setup on your computer

Expand Down
135 changes: 135 additions & 0 deletions bin/check-content
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
#!/usr/bin/env python3
"""Validate the front matter in content/.

Catches the mistakes that a Hugo build does not: a person id that does not
match its filename, a duplicate id, an event that points at a person or
sponsor page which does not exist, and a declared photo with no file behind it.

Exit code 1 if any error is found. Warnings do not fail the build.

Usage:
bin/check-content
"""

import pathlib
import re
import sys

import yaml

ROOT = pathlib.Path(__file__).resolve().parent.parent
CONTENT = ROOT / "content"
PHOTOS = ROOT / "static" / "images" / "people"

FRONT_MATTER = re.compile(r"\A---\r?\n(.*?)\r?\n---[ \t]*(?:\r?\n|\Z)", re.S)

errors = []
warnings = []


def error(path, message):
errors.append(f"{path.relative_to(ROOT)}: {message}")


def warn(path, message):
warnings.append(f"{path.relative_to(ROOT)}: {message}")


def front_matter(path):
"""Return the parsed front matter, or None if the file has none."""
text = path.read_text(encoding="utf-8")
match = FRONT_MATTER.match(text)
if not match:
if text.lstrip().startswith("+++"):
return None # TOML front matter, not used by content/ today
error(path, "no YAML front matter")
return None
try:
return yaml.safe_load(match.group(1)) or {}
except yaml.YAMLError as exc:
error(path, f"front matter is not valid YAML: {exc}")
return None


def collect_ids(section):
"""Map id -> path for every page in a section, reporting id problems."""
found = {}
for path in sorted((CONTENT / section).glob("*.md")):
if path.name == "_index.md":
continue
data = front_matter(path)
if data is None:
continue
page_id = data.get("id")
if not page_id:
error(path, "missing 'id'")
continue
if page_id != path.stem:
error(path, f"id '{page_id}' does not match filename '{path.stem}'")
if page_id in found:
error(path, f"duplicate id '{page_id}', already used by {found[page_id].name}")
found[page_id] = path
return found


def check_photos(people):
for page_id, path in people.items():
data = front_matter(path) or {}
photo = data.get("photo")
if photo and not (PHOTOS / photo).exists():
error(path, f"photo '{photo}' not found in static/images/people/")


def referenced_ids(data, key):
"""Pull every id out of an event's people_sections or sponsor_levels."""
out = []
for section in data.get(key) or []:
for level in section.get("levels", [section]):
out.extend(level.get("people") or level.get("sponsors") or [])
return out


def check_events(people, sponsors):
for path in sorted((CONTENT / "events").rglob("*.md")):
data = front_matter(path)
if data is None:
continue
for ref in referenced_ids(data, "people_sections"):
if ref not in people:
error(path, f"references unknown person id '{ref}'")
for ref in referenced_ids(data, "sponsor_levels"):
if ref not in sponsors:
error(path, f"references unknown sponsor id '{ref}'")


def check_orphans(people):
"""A person page nothing links to renders, but nobody can reach it."""
body = "\n".join(
p.read_text(encoding="utf-8")
for p in CONTENT.rglob("*.md")
if p.parent.name != "people"
)
for page_id, path in people.items():
if not re.search(rf"\b{re.escape(page_id)}\b", body):
warn(path, f"person '{page_id}' is not referenced by any event or page")


def main():
people = collect_ids("people")
sponsors = collect_ids("sponsors")
check_photos(people)
check_events(people, sponsors)
check_orphans(people)

for line in warnings:
print(f"WARNING {line}")
for line in errors:
print(f"ERROR {line}")

print(f"\n{len(people)} people, {len(sponsors)} sponsors checked.")
print(f"{len(errors)} errors, {len(warnings)} warnings.")
return 1 if errors else 0


if __name__ == "__main__":
sys.exit(main())
19 changes: 0 additions & 19 deletions bin/publish

This file was deleted.

27 changes: 26 additions & 1 deletion config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,23 @@ baseURL = "https://pybcn.org"
languageCode = "en-us"
title = "PyBCN - The Barcelona Python Association"
theme = "pybcn_theme"
googleAnalytics = "UA-152706496-1"
enableGitInfo = true
enableRobotsTXT = true

# The site uses neither tags nor categories, and Hugo's defaults produced two
# empty index pages in the sitemap. The archive work will add its own taxonomy.
[taxonomies]

# llms.txt is a Hugo template, layouts/index.llms.txt, so it follows the site.
[outputFormats.llms]
mediaType = "text/plain"
baseName = "llms"
isPlainText = true
notAlternative = true

[outputs]
home = ["HTML", "RSS", "llms"]


[markup.goldmark.renderer]
unsafe=true
Expand All @@ -18,32 +34,41 @@ unsafe=true

[params.social_items]
[params.social_items.twitter]
name = "Twitter"
icon = "fab fa-twitter-square fa-2x"
link = "https://twitter.com/PyBCN"
[params.social_items.youtube]
name = "YouTube"
link = "https://www.youtube.com/c/PythonBarcelona"
icon = "fab fa-youtube-square fa-2x"
[params.social_items.meetup]
name = "Meetup"
link = "https://www.meetup.com/python-barcelona/"
icon = "fab fa-meetup fa-2x"
[params.social_items.github]
name = "GitHub"
link = "https://github.com/pybcn"
icon = "fab fa-github fa-2x"
[params.social_items.linkedin]
name = "LinkedIn"
link = "https://linkedin.com/company/pybcn"
icon = "fab fa-linkedin fa-2x"
[params.social_items.slack]
name = "Slack"
link = "https://pybcn.slack.com"
icon = "fab fa-slack fa-2x"

[params.social_items_pyladies]
[params.social_items_pyladies.twitter]
name = "Twitter"
icon = "fab fa-twitter-square fa-2x"
link = "https://twitter.com/PyLadiesBCN"
[params.social_items_pyladies.meetup]
name = "Meetup"
link = "https://www.meetup.com/PyLadies-BCN"
icon = "fab fa-meetup fa-2x"
[params.social_items_pyladies.github]
name = "GitHub"
link = "https://github.com/pyladies-bcn"
icon = "fab fa-github fa-2x"

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading