Skip to content

Auto-publish scheduled blog posts on their date - #622

Open
madolson wants to merge 9 commits into
valkey-io:mainfrom
madolson:auto-publish-scheduled-blogs
Open

Auto-publish scheduled blog posts on their date#622
madolson wants to merge 9 commits into
valkey-io:mainfrom
madolson:auto-publish-scheduled-blogs

Conversation

@madolson

Copy link
Copy Markdown
Member

What

Adds an hourly GitHub Actions workflow that publishes blog posts once their date has arrived, so maintainers can merge a post ahead of time without it going live immediately.

How

  • Contributors/maintainers set draft = true in a post's frontmatter and set date to the intended publish date/time (UTC).
  • .github/workflows/publish-scheduled-blogs.yml runs hourly. build/publish-scheduled-blogs.py scans content/blog, and for any draft whose date has passed it removes the draft line and commits to main, which triggers the existing deploy.

This uses Zola's native draft support (not a custom field), so a pending post is excluded from the build, the blog listing, the sitemap, and the RSS feed until it is released. zola serve --drafts still previews it locally.

Why the app token

The commit is pushed with the valkeyrie-bot app token rather than GITHUB_TOKEN. Pushes made with GITHUB_TOKEN do not trigger other workflows, which would commit the post without ever rebuilding the site.

Notes / things to check before merge

  • The workflow needs secrets.VALKEYRIE_BOT_* usable from a schedule trigger and the app to have contents: write on this repo. Recommend a first run via Run workflow with dry_run ticked to confirm.
  • main is currently unprotected, so the bot can push directly. If branch protection is added later, this will need a bypass or a PR-based flow.
  • Drafts with no date are never auto-published, so work-in-progress can safely sit on main.

Verified

  • Script tested against the real repo's frontmatter variants (date = / date=, with/without times), nested index.md posts, draft under a [extra] table (correctly ignored), missing dates, and idempotency.
  • End-to-end: a draft post was confirmed absent from build/listing/sitemap/feed, then present after the script released it and the site rebuilt.
  • zola serve with and without --drafts confirmed the live vs. preview behavior.

This was generated by AI but verified, with love, by a human.

Adds an hourly workflow that releases blog posts marked draft = true once
their frontmatter date has passed, by removing the draft line and committing
to main. This lets maintainers merge posts ahead of time without them going
live immediately.

Uses Zola's native draft support, so pending posts are excluded from the
build, blog listing, sitemap, and RSS feed until released. The workflow
pushes with the valkeyrie-bot app token so the existing deploy workflow
triggers on the resulting commit.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
@madolson
madolson requested a review from stockholmux as a code owner July 31, 2026 18:53
@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request adds a script that publishes due blog drafts, a scheduled and manually dispatched workflow that runs and commits releases, and contributor documentation for date-based scheduling and draft publication.

Changes

Scheduled Blog Publication

Layer / File(s) Summary
Scheduled draft release logic
build/publish-scheduled-blogs.py
The script scans blog posts, parses TOML frontmatter, validates UTC publication dates, removes eligible top-level draft = true keys, preserves file bytes and line endings, and reports released paths.
Workflow execution and commit
.github/workflows/publish-scheduled-blogs.yml
The workflow runs daily or manually, supports dry-run execution, generates a GitHub App token, conditionally commits and pushes published posts, and writes results to the workflow summary.
Contributor scheduling guidance
CONTRIBUTING-BLOG-POST.md
The contributor guide documents date-only frontmatter, maintainer-controlled dates, draft posts, UTC scheduling, and manual publication.

Possibly related issues

  • #554 — The pull request implements the issue’s scheduled blog publication workflow, including date-based frontmatter handling and automated draft release.

Suggested reviewers: stockholmux


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/publish-scheduled-blogs.yml:
- Line 49: Update the publishing step around PUBLISHED to stage the content/blog
directory directly instead of piping published paths through default xargs and
git add. Preserve the existing clean-checkout assumptions and ensure filenames
containing spaces are handled safely.
- Around line 25-30: Update the Generate token step using
actions/create-github-app-token to explicitly request only the contents
permission, preserving the existing app-id and private-key inputs so the token
remains limited to checkout and pushing repository files.

In `@build/publish-scheduled-blogs.py`:
- Around line 42-49: Update strip_draft to preserve the input frontmatter’s
original line-ending style when removing the matched top-level draft line. Avoid
relying on read_text() and "\n".join() normalization in the surrounding release
flow, including the code paths around lines 64–97, so CRLF-authored posts retain
CRLF endings and unchanged content is not rewritten.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c352e5cf-7240-4fae-8362-1ef25a390d84

📥 Commits

Reviewing files that changed from the base of the PR and between c4a402f and e6eaf53.

📒 Files selected for processing (3)
  • .github/workflows/publish-scheduled-blogs.yml
  • CONTRIBUTING-BLOG-POST.md
  • build/publish-scheduled-blogs.py

Comment thread .github/workflows/publish-scheduled-blogs.yml
Comment thread .github/workflows/publish-scheduled-blogs.yml Outdated
Comment thread build/publish-scheduled-blogs.py Outdated
- Scope the app token with permission-contents: write, since the workflow
  permissions block only constrains GITHUB_TOKEN.
- Stage content/blog directly instead of piping paths through xargs, which
  splits on whitespace and would break on filenames containing spaces.
- Read and write with newline="" so CRLF-authored posts keep their line
  endings and produce a one-line diff. content/blog already contains one
  CRLF file, which previously would have been rewritten whole. Also strip
  the resulting lone trailing carriage return before parsing, which tomllib
  rejects.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>

@stockholmux stockholmux left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm willing to give this a try but I think this is going to be a foot-gun for unintended publishing.

As for the code itself, I'd rather not see regular expression parsing on toml but rather the proper parsing library you're already using.

Finally, I think we have some process issues: we shouldn't merge work in progress and we need to make sure the blog process is very, very explicit about UTC posting times.

Comment thread build/publish-scheduled-blogs.py Outdated
Comment thread build/publish-scheduled-blogs.py Outdated
Comment thread build/publish-scheduled-blogs.py Outdated
Comment thread CONTRIBUTING-BLOG-POST.md Outdated
madolson added 3 commits July 31, 2026 12:47
Parse frontmatter entirely with tomllib. The regex module is no longer
imported: the fences are found by string search, and each candidate line is
validated by tomllib itself, which is what authoritatively distinguishes the
real top-level 'draft = true' from one in a table, a comment, or a string.
Quoted date values are re-parsed as bare TOML instead of a strptime loop, so
both forms follow identical rules. This also fixes two files the regex
mishandled: a post with a leading blank line before the fence, and one with
a multi-line array in its frontmatter.

Publish once a day at 16:00 UTC (08:00 PST / 09:00 PDT) instead of hourly.
Posts are conventionally dated 00:00:00 or 01:01:01, so an hourly job would
have published them in the middle of the night PT.

Document that dates are UTC and that the time of day in 'date' does not
control publication, and drop the suggestion that work-in-progress can be
parked on main.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
The publish job ignores the time of day, so a bare date is the clearer
convention. 16 posts already use one.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@build/publish-scheduled-blogs.py`:
- Around line 117-125: Update the date comparison in the scheduled publication
flow around publish_at and now to normalize both aware datetimes to UTC, then
compare their date() values rather than full timestamps. Preserve the existing
future-date hold behavior while allowing posts dated today to publish regardless
of their publication time.

In `@CONTRIBUTING-BLOG-POST.md`:
- Around line 74-77: Clarify the date guidance in the `date` field section so it
uses one consistent rule: identify whether contributors should enter the
intended publication date or maintainers assign the final publication date, and
align the wording with the scheduling behavior that publishes drafts when `date`
arrives.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 74b47526-97ec-4594-a529-e6274cacc0d2

📥 Commits

Reviewing files that changed from the base of the PR and between e6eaf53 and 04226aa.

📒 Files selected for processing (3)
  • .github/workflows/publish-scheduled-blogs.yml
  • CONTRIBUTING-BLOG-POST.md
  • build/publish-scheduled-blogs.py

Comment thread build/publish-scheduled-blogs.py Outdated
Comment thread CONTRIBUTING-BLOG-POST.md
madolson added 2 commits July 31, 2026 13:01
The line-scan approach had a real bug: a 'draft = true' inside a multi-line
string was removed instead of the actual key, corrupting the value and
leaving the post a draft. 8 existing posts use multi-line frontmatter
values, so this was reachable. Now a candidate line is removed and the
result re-parsed, accepting the edit only if it drops 'draft' and changes
nothing else.

Invalid TOML, a missing date, and an unparseable date now exit non-zero
instead of warning and skipping. 'zola build' already fails on bad TOML,
so there is no reason to accept it quietly here.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
tomlkit is a round-trip TOML parser, so 'del frontmatter["draft"]' replaces
the hand-rolled line scanning and the remove-then-re-parse verification.
Reading the key directly only sees the top-level table, so the table-header
scan is gone too, along with the multi-line-value corruption it was working
around.

Verified tomlkit round-trips all 59 existing posts byte-identically, and
preserves comments and CRLF endings. Adds a pip install step; tomlkit is
MIT with no dependencies.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
@madolson

madolson commented Jul 31, 2026

Copy link
Copy Markdown
Member Author

@stockholmux Aight. Tried to make less of a foot gun. Always publishes at 15:00 UTC on the defined date, so we can schedule stuff out and not have to worry about individual availability. Rewrote a chunk of TOML parsing to use more of another dependency, to do less stuff manual.

This was generated by AI but verified, with love, by a human.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>
@madolson
madolson requested a review from stockholmux July 31, 2026 21:50
A post dated 2026-07-31 23:00:00 was held until the next day even though
the docs say the time of day is ignored. Compare date() values instead,
converting offset-aware datetimes to UTC first.

Also align the frontmatter comment with the scheduling section: date is the
publish date, not the day writing started.

Signed-off-by: Madelyn Olson <madelyneolson@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
build/publish-scheduled-blogs.py (1)

32-38: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Match only a complete closing frontmatter fence.

text.find(FENCE, start) matches +++ anywhere in TOML content. A valid value such as title = "C+++ guide" makes end point into the string. tomlkit.parse then receives truncated TOML and exits the release workflow.

Match the closing +++ only when it is a complete delimiter line. Support both LF and CRLF line endings.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@build/publish-scheduled-blogs.py` around lines 32 - 38, Update the
frontmatter parsing logic around the closing-fence lookup in the relevant parser
function so `+++` is recognized only when it forms a complete delimiter line,
not when embedded in TOML values. Match valid closing fences with both LF and
CRLF line endings, while preserving the existing `None` behavior when no
complete closing fence is found.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@build/publish-scheduled-blogs.py`:
- Around line 32-38: Update the frontmatter parsing logic around the
closing-fence lookup in the relevant parser function so `+++` is recognized only
when it forms a complete delimiter line, not when embedded in TOML values. Match
valid closing fences with both LF and CRLF line endings, while preserving the
existing `None` behavior when no complete closing fence is found.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c8befeab-8b97-475d-9df9-2bf33ce527af

📥 Commits

Reviewing files that changed from the base of the PR and between 04226aa and f31aa8e.

📒 Files selected for processing (3)
  • .github/workflows/publish-scheduled-blogs.yml
  • CONTRIBUTING-BLOG-POST.md
  • build/publish-scheduled-blogs.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • CONTRIBUTING-BLOG-POST.md
  • .github/workflows/publish-scheduled-blogs.yml

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants