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
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ vendor
.bundle
Gemfile.lock
node_modules
assets/lib
1 change: 1 addition & 0 deletions .github/workflows/pages-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true

- name: Setup Pages
id: pages
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ jobs:
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: true

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: 3.3
bundler-cache: true

- name: Check post format
run: ruby tools/check_post_format.rb

- name: Build site
run: bundle exec jekyll b -d "_site"
env:
Expand Down
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,21 @@ Built with [Jekyll](https://jekyllrb.com) + [Chirpy](https://github.com/cotes202

Requires [Docker](https://www.docker.com/) — no local Ruby/Jekyll install needed.

The theme's static assets (fonts, JS libs) are self-hosted via a git submodule
([`assets/lib`](assets/lib)), so clone with `--recurse-submodules`:

```bash
git clone --recurse-submodules https://github.com/allexistence/allexistence.github.io.git
```

If you already cloned without it:

```bash
git submodule update --init
```

Then:

```bash
docker compose up
```
Expand Down Expand Up @@ -63,3 +78,15 @@ Then write the post in Markdown below the front matter. A few notes:
on per post.
- To link out to an externally-hosted post instead of writing one here, see any of the existing
Medium-redirect posts in `_posts/` for the pattern (a `<script>` redirect plus a fallback link).

### Checking your post is formatted correctly

Before opening a PR, verify your post's filename, folder, and front matter are all correct:

```bash
docker compose exec site ruby tools/check_post_format.rb
```

It checks that every post: has a `YYYY-MM-DD-slug.md` filename, lives in a matching `YYYY-MM-DD/`
folder, has valid YAML front matter, and has a non-empty `title`, `date`, `categories`, and `tags`.
This also runs automatically as a CI check on every pull request.
2 changes: 1 addition & 1 deletion _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ comments:
# Self-hosted static assets, optional › https://github.com/cotes2020/chirpy-static-assets
assets:
self_host:
enabled: # boolean, keep empty means false
enabled: true # boolean, keep empty means false
# specify the Jekyll environment, empty means both
# only works if `assets.self_host.enabled` is 'true'
env: # [development | production]
Expand Down

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions assets/lib
Submodule lib added at 5cde3f
100 changes: 100 additions & 0 deletions tools/check_post_format.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Validates every post in _posts/ against this repo's conventions:
# - filename matches YYYY-MM-DD-slug.md
# - lives in a _posts/YYYY-MM-DD/ folder matching that same date
# - has valid YAML front matter with title, date, categories, tags
# - categories/tags are non-empty arrays
#
# Usage: ruby tools/check_post_format.rb

require "yaml"
require "date"

POSTS_DIR = File.join(__dir__, "..", "_posts")
FILENAME_RE = /\A(\d{4}-\d{2}-\d{2})-.+\.md\z/

errors = []
checked = 0

Dir.glob(File.join(POSTS_DIR, "**", "*.md")).sort.each do |path|
checked += 1
rel = path.sub("#{POSTS_DIR}/", "")
filename = File.basename(path)
folder = File.basename(File.dirname(path))

# filename convention
match = FILENAME_RE.match(filename)
unless match
errors << "#{rel}: filename must match YYYY-MM-DD-slug.md"
next
end

file_date = match[1]

# folder convention
if folder != file_date
errors << "#{rel}: lives in folder '#{folder}/' but filename date is '#{file_date}' — should be _posts/#{file_date}/#{filename}"
end

content = File.read(path)

unless content.start_with?("---\n") || content.start_with?("---\r\n")
errors << "#{rel}: missing front matter (must start with '---')"
next
end

parts = content.split(/^---\s*$/, 3)
if parts.length < 3
errors << "#{rel}: front matter block is not closed with a second '---'"
next
end

front_matter = begin
YAML.safe_load(parts[1], permitted_classes: [Date, Time])
rescue Psych::SyntaxError => e
errors << "#{rel}: invalid YAML in front matter — #{e.message}"
next
end

unless front_matter.is_a?(Hash)
errors << "#{rel}: front matter did not parse to a set of key/value pairs"
next
end

# required fields
title = front_matter["title"]
if title.nil? || !title.is_a?(String) || title.strip.empty?
errors << "#{rel}: missing or empty 'title'"
end

date = front_matter["date"]
if date.nil?
errors << "#{rel}: missing 'date'"
end

categories = front_matter["categories"]
if categories.nil? || !categories.is_a?(Array) || categories.empty?
errors << "#{rel}: 'categories' must be a non-empty list, e.g. [Category Name]"
end

tags = front_matter["tags"]
if tags.nil? || !tags.is_a?(Array) || tags.empty?
errors << "#{rel}: 'tags' must be a non-empty list, e.g. [tag-one, tag-two]"
end
end

if checked.zero?
puts "No posts found under #{POSTS_DIR} — nothing to check."
exit 0
end

if errors.empty?
puts "All #{checked} post(s) passed format checks."
exit 0
else
puts "#{errors.size} problem(s) found across #{checked} post(s):"
errors.each { |e| puts " ✗ #{e}" }
exit 1
end
Loading