diff --git a/.github/workflows/publish-fastmcp.yml b/.github/workflows/publish-fastmcp.yml index dc32179cb..8b2ce33b2 100644 --- a/.github/workflows/publish-fastmcp.yml +++ b/.github/workflows/publish-fastmcp.yml @@ -178,19 +178,27 @@ jobs: run: uv publish -v dist/fastmcp-*.tar.gz dist/fastmcp-*.whl update-published-docs: - name: Update published-docs branch + name: Open published-docs PR runs-on: ubuntu-latest needs: pypi-publish if: github.event_name == 'workflow_run' && github.event.workflow_run.event == 'release' && needs['pypi-publish'].outputs.is_prerelease != 'true' - timeout-minutes: 2 + timeout-minutes: 5 permissions: - contents: write + contents: read steps: + - name: Generate Marvin App token + id: marvin-token + uses: actions/create-github-app-token@v3 + with: + app-id: ${{ secrets.MARVIN_APP_ID }} + private-key: ${{ secrets.MARVIN_APP_PRIVATE_KEY }} + - uses: actions/checkout@v7 with: fetch-depth: 0 ref: ${{ github.event.workflow_run.head_sha }} + token: ${{ steps.marvin-token.outputs.token }} - name: Check release line id: release_line @@ -205,6 +213,26 @@ jobs: echo "Release commit is not on ${DEFAULT_BRANCH}; skipping published-docs update." fi - - name: Point published-docs at published release + - name: Prepare published docs tree if: steps.release_line.outputs.update_published_docs == 'true' - run: git push --force origin "HEAD:published-docs" + env: + RELEASE_SHA: ${{ github.event.workflow_run.head_sha }} + run: | + git fetch origin published-docs + git switch --force-create published-docs-sync origin/published-docs + git read-tree --reset -u "$RELEASE_SHA" + test "$(git write-tree)" = "$(git rev-parse "${RELEASE_SHA}^{tree}")" + + - name: Open published docs PR + if: steps.release_line.outputs.update_published_docs == 'true' + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ steps.marvin-token.outputs.token }} + base: published-docs + branch: marvin/publish-docs-v${{ needs.pypi-publish.outputs.version }} + commit-message: "Publish FastMCP v${{ needs.pypi-publish.outputs.version }} docs" + title: "Publish FastMCP v${{ needs.pypi-publish.outputs.version }} docs" + body: "Updates `published-docs` to the exact release tree. Merging publishes the documentation to production." + delete-branch: true + author: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>" + committer: "marvin-context-protocol[bot] <225465937+marvin-context-protocol[bot]@users.noreply.github.com>" diff --git a/CLAUDE.md b/CLAUDE.md index 5a4ef8bed..1a2cbddb0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -117,7 +117,9 @@ Set `target_commitish` to the same branch that will receive the release tag. For **Patch releases** (3.1.1, 3.0.2) get 1-2 sentences explaining what broke and what the fix does. Keep it minimal — the auto-generated changelog has the details. -**Merge the docs changelog PR *before* cutting the release, not after.** The post-publish `update-published-docs` job force-pushes the `published-docs` branch (which gofastmcp.com serves) to the released commit for stable releases on the default branch, so the changelog entry only reaches the live site if it's already in the commit being tagged. Land the docs PR on the release target branch first, then cut the release from that branch. If you tag first and merge docs after, this release's changelog won't appear on the live site until the next default-branch stable release force-pushes `published-docs` forward. Maintenance releases from `release/3.x` or `release/2.x` publish packages and GitHub notes without repointing `published-docs`; add their changelog entries on the maintenance branch, slotted into the matching major-version section. Two hand-maintained files mirror the GitHub release and must get a new entry for every version, newest at the top (these are `.mdx` and are not covered by the prek Prettier hook, which only runs on `yaml`/`json5` — match the existing entries' style by hand): +**Publish docs through a PR.** The `published-docs` branch serves gofastmcp.com, and repository rules reject direct pushes and force-pushes to it. Stable releases from `main` automatically open a publication PR after PyPI succeeds. For prereleases and later docs follow-ups, create the same PR manually: start a temporary branch from the current `published-docs`, make a single commit whose tree exactly matches the desired commit on `main`, and use `published-docs` as the PR base. Merging publishes to production. Never push directly to `published-docs`. + +**Merge the docs changelog PR *before* cutting the release, not after.** The post-publish `update-published-docs` job opens a PR that syncs `published-docs` to the released commit for stable releases on the default branch, so the changelog entry only reaches the live site if it's already in the commit being tagged. Land the docs PR on the release target branch first, then cut the release from that branch. If you tag first and merge docs after, this release's publication PR will not include the changelog; publish `main` manually through the PR flow above or wait for the next default-branch stable release. Maintenance releases from `release/3.x` or `release/2.x` publish packages and GitHub notes without repointing `published-docs`; add their changelog entries on the maintenance branch, slotted into the matching major-version section. Two hand-maintained files mirror the GitHub release and must get a new entry for every version, newest at the top (these are `.mdx` and are not covered by the prek Prettier hook, which only runs on `yaml`/`json5` — match the existing entries' style by hand): - `docs/changelog.mdx` is the full mirror. Add an `` block with: a bold linked title (`**[v: ]()**`), a condensed 1-paragraph intro (one sentence for patches), the full categorized PR list reformatted from the `--generate-notes` output (`* by [@user](https://github.com/user) in [#NNNN](<pull-url>)`), a `## New Contributors` list (plain `@user`, linked PR), and a `**Full Changelog**: [vA...vB](<compare-url>)` line. - `docs/updates.mdx` is the skimmable card feed. Add an `<Update label="FastMCP <version>" description="Month DD, YYYY" tags={["Releases"]}>` wrapping a `<Card>` that links to the GitHub release, with a 1-2 sentence summary and (for point releases) a handful of emoji-bulleted highlights. diff --git a/docs/development/releases.mdx b/docs/development/releases.mdx index ecba9a25f..f537703e2 100644 --- a/docs/development/releases.mdx +++ b/docs/development/releases.mdx @@ -65,7 +65,7 @@ Our release process is intentionally simple: 2. Generate release notes automatically, and curate or add additional editorial information as needed 3. GitHub releases automatically trigger PyPI deployments -Current-major releases target `main`. Maintenance releases target their release branch, such as `release/3.x` for 3.x patches and `release/2.x` for 2.x patches. Stable releases from `main` update the `published-docs` branch after PyPI publishing succeeds; maintenance releases publish packages and GitHub release notes without repointing the live docs branch. +Current-major releases target `main`. Maintenance releases target their release branch, such as `release/3.x` for 3.x patches and `release/2.x` for 2.x patches. Stable releases from `main` open a PR that syncs the release commit to `published-docs` after PyPI publishing succeeds; merging that PR publishes the live docs. Prereleases skip the automatic PR and use the same PR-based sync when their docs are ready to publish. Maintenance releases publish packages and GitHub release notes without repointing the live docs branch. This automation lets maintainers focus on code quality rather than release mechanics.