Minimal Bridgetown 2.x scaffold listing open source projects from src/_data/projects.yml.
# from project root
bundle install
bundle exec bridgetown startOpen http://localhost:4000 in your browser.
src/_data/projects.yml— production project list (the one the site builds from)src/_data/projects_dev.yml— dev/test project list (kept in sync byscripts/update_projects)src/index.html— page that lists projectssrc/_layouts/default.html— basic layout
All scripts live in scripts/ and are meant to be run from the project root.
Iterates over every project in src/_data/projects.yml and fetches up-to-date
data from GitHub, GitLab, Codeberg, and RubyGems APIs, then writes the results
back to both projects.yml and projects_dev.yml.
| Variable | Required | Purpose |
|---|---|---|
GITHUB_TOKEN |
Recommended | Raises the GitHub API rate limit from 60 to 5,000 req/hr |
GITLAB_TOKEN |
Optional | Authenticates GitLab API requests |
RUBYGEMS_HANDLE |
Recommended | Your RubyGems username (e.g. pboling) — enables gem discovery |
Set these in a .env.local file (or however your shell loads env vars) before running.
ruby scripts/update_projectsWhen run with no subcommand or field, the script:
- Discovers any gems on RubyGems.org (owned by
RUBYGEMS_HANDLE) not yet inprojects.yml, displays them, and asks whether to add them now. - Shows how many projects will be updated vs. skipped (projects scraped within the last 24 hours are skipped automatically).
- Prompts for confirmation before starting the update loop.
Discovery only runs on a bare full update. Passing a surgical field or the
add_projectsubcommand disables it automatically.
To skip all prompts (e.g. in CI or a cron job), pass -y / --no-tty:
ruby scripts/update_projects -yTo skip the RubyGems discovery pre-flight explicitly:
ruby scripts/update_projects --no-discoverTo force a re-scrape of a project that was recently scraped, delete or zero out
its last_scrape_at field in projects.yml.
Instead of a full re-scrape, update a single field for every project.
Surgical mode ignores last_scrape_at timestamps and skips gem discovery.
ruby scripts/update_projects <field>Valid fields:
| Field | Source |
|---|---|
github_stars |
GitHub API |
gitlab_stars |
GitLab API |
codeberg_stars |
Codeberg / Gitea API |
total_downloads |
RubyGems API |
daily_downloads |
RubyGems API |
release_downloads |
RubyGems API |
release_date |
RubyGems API (Ruby gems) / GitHub Releases (others) |
last_commit_on |
GitHub API |
status |
Derived from GitHub (active / stale / archived) |
ruby scripts/update_projects github_stars
ruby scripts/update_projects release_date
ruby scripts/update_projects -y status # non-interactiveWizard that interrogates you for a project's details, auto-fills everything it
can from the relevant package registry and GitHub, then shows you the resulting
YAML entry for confirmation before writing it to projects.yml.
Works for any language and any of the supported package ecosystems: RubyGems, Cargo (Rust / crates.io), npm (JavaScript/TypeScript), PyPI (Python), Go Modules (proxy.golang.org), or none (forge-only projects with no package registry).
# Fully interactive — prompts for everything
ruby scripts/update_projects add_project
# Pre-supply values to skip individual prompts
ruby scripts/update_projects add_project \
--name my-crate \
--ecosystem cargo \
--role contributor \
--github-url https://github.com/org/my-crate
# Non-interactive (auto-accept all defaults / pre-supplied values)
ruby scripts/update_projects add_project -y \
--name my-gem \
--ecosystem rubygems \
--role authoradd_project flags (all optional — omitted values are prompted interactively):
| Flag | Description |
|---|---|
--name NAME |
Project / package name |
--ecosystem ECO |
rubygems | cargo | npm | pypi | go | none |
--language LANG |
Primary language (inferred from ecosystem if omitted) |
--role ROLE |
author | contributor | maintainer (default: contributor) |
--github-url URL |
GitHub repository URL |
--gitlab-url URL |
GitLab repository URL |
--codeberg-url URL |
Codeberg repository URL |
--description TEXT |
Short description (HTML allowed) |
--minimum-version VER |
Minimum runtime version |
--tags TAG1,TAG2 |
Comma-separated tags |
--funding-sites VALUE |
Pre-set funding sites. Use none for an empty list, or TYPE1:URL1,TYPE2:URL2 (e.g. OpenCollective:https://opencollective.com/myorg). Defaults to OpenCollective + Liberapay for role: author, empty list otherwise. |
| Flag | Default | Description |
|---|---|---|
-h, --help |
Show help and exit | |
--no-discover |
(auto) | Skip the RubyGems discovery pre-flight |
-y, --no-tty |
(interactive) | Auto-accept all confirmation prompts |
update_projects refuses to run if src/_data/projects_dev.yml is absent.
That file's absence means scripts/devswap was run but not run again to
restore the production file — see the next section.
Read-only script for filtering, auditing, and exploring src/_data/projects.yml.
No API calls are made; it operates entirely on the local YAML file.
Output is rendered by the table_tennis gem,
which provides auto-layout to fit your terminal, auto-formatted numbers (commas, — for nil),
color_scales on numeric columns, mark to highlight rows by status, and automatic
light/dark theme detection.
bundle exec ruby scripts/project_query <subcommand> [args] [options]| Subcommand | Description |
|---|---|
list |
Table of all projects |
stats |
Summary statistics (counts by language, ecosystem, status, role; stars; downloads) |
show <name> |
Full YAML dump for one project (fuzzy name match) |
missing <field> |
Projects where the given field is blank or nil |
stale [--days N] |
Projects not scraped in > N days (default: 30) |
status <value> |
Filter by status (active | stale | archived) |
language <value> |
Filter by language |
ecosystem <value> |
Filter by ecosystem (rubygems | cargo | npm | pypi | go) |
role <value> |
Filter by role (author | contributor | maintainer) |
tag <value> |
Filter by tag |
needs-attention |
Full data-quality report: missing fields, stale scrapes, status mismatches, zero downloads, etc. |
console |
Drop into an IRB session with project data pre-loaded |
| Flag | Default | Description |
|---|---|---|
--days N |
30 | Day threshold for the stale subcommand |
--format table|names|yaml |
table |
Output format |
--file PATH |
src/_data/projects.yml |
Use a different YAML file |
--no-color |
(auto) | Disable ANSI colour output |
-h, --help |
Show help |
# What needs fixing right now?
bundle exec ruby scripts/project_query needs-attention
# Which projects have no ecosystem set?
bundle exec ruby scripts/project_query missing ecosystem
# Which projects haven't been scraped in over a week?
bundle exec ruby scripts/project_query stale --days 7
# Show full entry for one project
bundle exec ruby scripts/project_query show version_gem
# Get just the names of all stale projects (for scripting)
bundle exec ruby scripts/project_query status stale --format names
# Interactive exploration
bundle exec ruby scripts/project_query consoleThe console subcommand drops into a pre-loaded IRB session with:
PROJS— array of typedProjectstructs (one per project)PROJECTS— array of raw hashes
Helper methods available at the prompt:
| Method | Description |
|---|---|
table(projs, **opts) |
Print a TableTennis table from any Project array; accepts all table_tennis options |
find("name") |
Fuzzy-find a project by name |
by_language("Ruby") |
Filter by language |
by_status("stale") |
Filter by status |
by_role("author") |
Filter by role |
by_ecosystem("rubygems") |
Filter by ecosystem |
by_tag("rspec") |
Filter by tag |
missing(:field) |
Projects with a blank field |
stale_scrape(days: 30) |
Not scraped in N days |
stale_commit(days: 365) |
No commit in N days |
never_scraped |
Never been scraped |
needs_attention |
Returns grouped issues hash |
stats |
Prints summary statistics |
Each Project struct also has convenience predicates: ruby_gem?, active?,
archived?, stale?, never_scraped?, days_since_scrape, days_since_commit,
github_url.
Reads src/_data/projects.yml, reports the tag frequency distribution, and can
merge/rename tags to reduce the total unique count and keep filter pills manageable.
bundle exec ruby scripts/analyze_tags [OPTIONS]| Mode | Behaviour |
|---|---|
| (default — no flags) | Print the full analysis report: overview stats, frequency table, suggested merges, impact estimate |
--interactive |
Walk through each suggested merge one-by-one and confirm/reject/customize |
--interactive -y |
Auto-accept all suggested merges non-interactively |
--apply RENAMES_FILE |
Apply a hand-crafted YAML renames file |
| Flag | Description |
|---|---|
-i, --interactive |
Interactive merge wizard |
-y, --no-tty |
Auto-accept all prompts |
--dry-run |
Preview what would change without writing files |
--apply PATH |
Apply renames from a YAML file (old_tag: canonical_tag) |
--no-color |
Disable ANSI colour output |
--file PATH |
Use a different projects YAML file |
--dev-file PATH |
Use a different projects_dev YAML file |
# tmp/my_renames.yml
testing: test
rspec: test
auth: security
debug: log# See the full spread analysis and all suggested merges
bundle exec ruby scripts/analyze_tags
# Interactively accept or reject each merge (can also type a custom canonical name)
bundle exec ruby scripts/analyze_tags --interactive
# Preview what all suggested merges would do, without writing
bundle exec ruby scripts/analyze_tags --interactive -y --dry-run
# Apply all suggested merges without being asked
bundle exec ruby scripts/analyze_tags --interactive -y
# Apply a hand-crafted set of renames
bundle exec ruby scripts/analyze_tags --apply tmp/my_renames.ymlThe script always updates both projects.yml and projects_dev.yml when applying changes.
Provides a full interactive terminal UI for organising which projects belong to which family, renumbering positions, creating new families, and moving projects between families.
bundle exec ruby scripts/manage_families
bundle exec ruby scripts/manage_families --dry-run # preview without writing| Key | Action |
|---|---|
1–13 |
Open a family to manage its members |
U |
Manage unassigned projects (assign any to an existing or new family) |
N |
Create a new family (prompts for ID + display name, then seeds members) |
Q |
Save all changes and exit |
| Key | Action |
|---|---|
R |
Renumber — compact family_position to 1..N in the current order, fixing gaps and duplicates |
M |
Move member — swap a member's position with another, or unassign it entirely |
A |
Add unassigned — pick from the unassigned project list and append to this family |
D |
Delete family — unassigns all members (they return to the unassigned pool) |
P |
Reorder globally — shift this family's positions so it sorts before/after others on the page |
B |
Back to main menu |
family_position is a per-family integer (1-based) determining the stacking order of
cards within that family's stack on the page. The rendering order of family stacks
themselves is determined by the insertion order in the YAML (i.e. which family appears
first in projects.yml).
Use R (renumber) to fix duplicate or gapped positions after any edits.
| Flag | Description |
|---|---|
--dry-run |
Show what would be saved without writing files |
--no-color |
Disable ANSI colour output |
--file PATH |
Use a different projects YAML file |
--dev-file PATH |
Use a different projects_dev YAML file |
Changes are written to both projects.yml and projects_dev.yml on exit.
The dev file only receives family-field updates for projects that already exist there.
Toggles projects.yml between the production and dev/test versions so you can
preview dev-only projects locally without risking them being deployed.
# Swap to dev (projects_dev.yml becomes projects.yml for local preview)
scripts/devswap
# Swap back to prod (restores the original projects.yml)
scripts/devswapState machine:
| Before | After |
|---|---|
projects.yml (prod) + projects_dev.yml |
projects.yml (dev) + projects_prod.yml |
projects.yml (dev) + projects_prod.yml |
projects.yml (prod) + projects_dev.yml |
⚠️ Always runscripts/devswapa second time to restore the production file before committing or deploying.scripts/update_projectswill abort with a clear error if you forget.
For each project in projects.yml, this script performs an HTTP HEAD request to
https://<project-name>.galtzo.com and sets the docs_site field to that URL
if the subdomain resolves, or null if it doesn't.
# In-place update (creates a timestamped .bak backup automatically)
ruby scripts/generate_docs_sites.rb
# Preview changes without writing anything
ruby scripts/generate_docs_sites.rb --dry-run
# Write to a separate output file instead of modifying projects.yml
ruby scripts/generate_docs_sites.rb --output /tmp/projects_updated.yml
# Only check the first 5 projects (useful for testing)
ruby scripts/generate_docs_sites.rb --limit 5
# Use a different projects file
ruby scripts/generate_docs_sites.rb --file path/to/other.yml
# Adjust timeouts / redirect limits
ruby scripts/generate_docs_sites.rb --timeout 10 --max-redirects 3Full option reference (--help):
| Flag | Default | Description |
|---|---|---|
-f / --file PATH |
src/_data/projects.yml |
Path to the projects YAML file |
-o / --output PATH |
(in-place) | Write output here instead of modifying the input file |
--dry-run |
false | Print a summary of changes without writing |
--timeout N |
5 | HTTP open/read timeout in seconds |
--max-redirects N |
5 | Maximum redirects to follow per HEAD request |
--limit N |
(all) | Only process the first N projects |
-h / --help |
Show help |