Skip to content

internal: Add yarn blog:publish and a CI check for blog post dates - #4241

Merged
ntucker merged 13 commits into
masterfrom
claude/blog-publish-date-9h52n9
Oct 6, 2026
Merged

ntucker merged 13 commits into
masterfrom
claude/blog-publish-date-9h52n9

Conversation

@ntucker

@ntucker ntucker commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Requested by Nathaniel · project thread

Before: a blog post's date and URL come from its filename prefix, and nothing touched it on publish. Removing draft: true from 2026-10-03-v0.19-batch-set.md would publish it dated Oct 3 at /blog/2026/10/03/..., whenever it actually shipped.

After: one command publishes a draft:

yarn blog:publish website/blog/2026-10-03-v0.19-batch-set.md

It removes draft: true, renames the file to today's date, rewrites every /blog/YYYY/MM/DD/<slug> link in the repo (the v0.19 post links to itself 19 times), and stages the post. The website check also fails a PR that publishes a post another way when its filename date is more than 3 days from today, and points at the command.

How: website/scripts/blog-publish.mjs publishes a post, or with --check <base> compares website/blog against the PR base sha to find posts published in the PR. The check runs in site-preview.yml before the install. Newly published posts must be dated by their filename alone (no date: field, no post folders) so the rename always applies. The blog rule now says to run yarn blog:publish. No changeset: website and CI only.

Tested locally on a copy of master: the draft passes the check; publishing it renames it to today, drops draft: true, rewrites all 19 links, and leaves it staged as a rename; an un-drafted post dated 2026-09-01 fails the check with the hint. Edge cases covered from review: renamed published posts, draft: true # comment, CRLF front matter, untracked posts and links, sibling slugs (slug-notes, slug.notes), and refusing to overwrite an existing file.

🤖 Generated with Claude Code

https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED


Note

Low Risk
Website tooling and CI guardrails only; no runtime library or auth changes.

Overview
Adds yarn blog:publish and a blog-publish.mjs helper so publishing a draft strips draft: true, renames the post to today’s YYYY-MM-DD- filename (which drives the URL), rewrites /blog/YYYY/MM/DD/<slug> links across the repo, and stages the result.

site-preview.yml runs blog-publish.mjs --check on pull requests (before yarn install) against the PR base SHA: newly published posts must use filename-only dating (no date: field, flat .md/.mdx files) and a filename date within 3 days of today, with an error that points authors at yarn blog:publish. CI and blog authoring docs are updated to describe the flow.

Reviewed by Cursor Bugbot for commit 4daee1e. Bugbot is set up for automated code reviews on this repo. Configure here.

Blog post dates and URLs come from the filename prefix, so removing
`draft: true` alone publishes the post with the day the draft was started.
website/scripts/blog-publish-date.mjs fails the website check for a post
published in the PR whose date is more than 3 days from today; --fix renames
it to today and rewrites its /blog/YYYY/MM/DD links.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED
@changeset-bot

changeset-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 4daee1e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@ntucker ntucker self-assigned this Oct 6, 2026
@vercel

vercel Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs-site Ready Ready Preview Oct 6, 2026 2:33pm UTC

Request Review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-06T14:39:36.501343Z 4daee1e New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

ntucker commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Direction looks right to me: a small no-install CI gate keyed off the PR base is simpler than changing how posts are named, and the rule tells agents exactly what to run.

One change for this PR: --fix crashes on any post that nothing links to by URL. git grep -l exits 1 when there are no matches, and execFileSync throws on a non-zero exit (checked: status 1). So for a post without self-links (the v0.19 post is unusual with 19; the coding-agents draft in #4237 likely has none), the script runs git mv, then dies with a stack trace and exit 1 before printing renamed ..., and skips any later posts in the loop. The rename does land, so a rerun passes, but an agent following the rule sees a failure and may try to undo it.

Smallest fix: treat grep's exit 1 as "no files", e.g.

let linking = [];
try {
  linking = git('grep', '-lF', oldUrl).split('\n').filter(Boolean);
} catch (e) {
  if (e.status !== 1) throw e;
}

Nothing else to change here.

…he post

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

ntucker commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Confirmed and fixed in 6273c7f: git grep exit 1 now means no linking files. Reproduced with a v0.19 copy that had its self-links stripped; --fix now prints renamed ... updated links in 0 file(s), exits 0, and the check then passes.


Generated by Claude Code

ntucker commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): Resolved at 6273c7f. The --fix path now treats git grep exit 1 as no linking files and rethrows anything else, which is exactly the fix asked for. No open change requests from me on this PR; LGTM.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4100b25c8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
…nted draft flags

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6765cfa134

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread .github/workflows/site-preview.yml
…d posts

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 74a6ca5091

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
…lug links

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1ed7ae0b01

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
…cked links, dotted slugs and post folders

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee12d91de9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish-date.mjs Outdated
Comment thread website/scripts/blog-publish-date.mjs Outdated
…cepts CRLF front matter

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED
It removes draft: true, renames the post to today and rewrites its links.
The CI check (blog-publish.mjs --check) now points failures at it, replacing --fix.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED
@ntucker ntucker changed the title internal: Fail CI when a blog post publishes with its draft date internal: Add yarn blog:publish and a CI check for blog post dates Oct 6, 2026
@codecov

codecov Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.10%. Comparing base (5898aff) to head (4daee1e).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4241   +/-   ##
=======================================
  Coverage   98.10%   98.10%           
=======================================
  Files         166      166           
  Lines        3166     3166           
  Branches      626      626           
=======================================
  Hits         3106     3106           
  Misses         18       18           
  Partials       42       42           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… draft regex

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9348c3d07f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish.mjs
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9348c3d. Configure here.

Comment thread website/scripts/blog-publish.mjs Outdated
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e1c37435ac

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread website/scripts/blog-publish.mjs Outdated
Comment thread website/scripts/blog-publish.mjs
Comment thread website/scripts/blog-publish.mjs Outdated
…uppercase draft booleans

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

ntucker commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Staff engineer (Cursor agent): CHANGE_THIS_PR at bc58117: the new git add -u -- ...linking in rewriteLinks crashes blog:publish whenever git grep --untracked returns an untracked file.

git add -u errors with pathspec '...' did not match any file(s) known to git for untracked paths instead of skipping them. That hits two ordinary cases:

  1. The post links to itself. By the time rewriteLinks runs, renameSync has already moved the post to the new name, which isn't tracked yet, so it shows up in linking.
  2. Any untracked file mentions the old URL, such as another draft that hasn't been git added yet.

Reproduced in a scratch repo with a committed draft whose body is [self](/blog/2026/09/01/post). The run exits 1 after the draft line is removed and the file renamed, but before the old path is git rm --cached and before the new one is staged. That leaves D 2026-09-01-post.md and ?? 2026-10-06-post.md, the half-published state that a6f6fdc was meant to prevent. With no self-link and only tracked linkers, it works as intended.

The smallest fix I verified is to stage only the tracked linkers. With this change the same repro exits 0, stages the tracked linker plus the rename, and leaves the untracked file rewritten but unstaged:

const tracked = git('ls-files', '-z', '--', ...linking).split('\0').filter(Boolean);
if (tracked.length) git('add', '--', ...tracked);

This is guarded by linking.length, because ls-files with an empty pathspec lists the whole repo. Dropping the new staging line and leaving the rewrites unstaged, as before, also works if you'd rather keep it simpler. The -I flag and the True/TRUE change to DRAFT look good.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016BYJtNfgrkJzBYthZA1WED

ntucker commented Oct 6, 2026

Copy link
Copy Markdown
Collaborator Author

Staff finding: fixed in this PR, in 4daee1e. I took the suggested ls-files fix, guarded by linking.length. Git 2.43 here accepts existing untracked paths with add -u, so I couldn't reproduce the crash locally, but newer git's pathspec error is plausible and this is safe on both. Tested your repro (a self-linking committed draft, a tracked linker and an untracked linker). It exits 0, stages the rename and the tracked linker, and leaves the untracked file rewritten but unstaged.


Generated by Claude Code

@ntucker
ntucker merged commit cfd3530 into master Oct 6, 2026
24 checks passed
@ntucker
ntucker deleted the claude/blog-publish-date-9h52n9 branch October 6, 2026 14:36

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4daee1e212

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// run this before installing packages.
function frontMatter(source) {
const block = source.match(FRONT_MATTER)?.[1] ?? '';
return { draft: DRAFT.test(block), hasDate: /^date:/m.test(block) };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Recognize the YAML forms Docusaurus parses

Fresh evidence beyond the earlier boolean/comment fixes is that valid YAML keys may be quoted or contain whitespace before the colon, such as date : 2026-01-01 or "draft": true; Docusaurus's gray-matter/js-yaml parser accepts these, but both regexes report the fields as absent. Consequently publish() can retain an explicit stale date while renaming and rewriting links for the filename date, and --check can classify removal of a spaced or quoted base draft key as an already-published post and skip date validation. Parse front matter consistently with Docusaurus or recognize these valid key forms.

Useful? React with 👍 / 👎.

This branch was successfully deployed

1 active deployment
Preview — 4daee1e2 Deployed Oct 6, 2026 by vercel[bot]
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