snapshot of https://github.com/tldraw/tldraw.git (main, 2026-09-23; blobs > 131072B omitted)
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
Some checks failed
Deploy analytics worker / Deploy analytics worker to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
i18n upload strings / Upload latest strings (push) Failing after 1s
Deploy bemo / Deploy bemo to ${{ (github.ref == 'refs/heads/bemo-production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Failing after 1s
Deploy grafana resources / ${{ github.event_name == 'push' && 'Deploy grafana resources' || 'Validate grafana resources (dry run)' }} (push) Failing after 0s
End to end tests (dotcom) / Check for relevant changes (push) Failing after 0s
End to end tests (examples) / Check for relevant changes (push) Failing after 0s
End to end tests (mermaid readability) / Check for relevant changes (push) Failing after 1s
End to end tests (perf) / Check for relevant changes (push) Failing after 0s
Publish editor extensions / Publish editor extensions (push) Failing after 1s
Publish SDK packages / Remove the publish-packages label (push) Has been skipped
Checks / Tests & checks (push) Has been cancelled
Checks / Build all projects (push) Has been cancelled
Deploy templates / Deploy templates (push) Has been cancelled
End to end tests (dotcom) / End to end tests (dotcom) (push) Has been cancelled
End to end tests (mermaid readability) / End to end tests (mermaid readability) (push) Has been cancelled
End to end tests (perf) / End to end tests (perf) (push) Has been cancelled
Deploy .com / Deploy dotcom to ${{ (github.ref == 'refs/heads/production' && 'production') || (github.ref == 'refs/heads/main' && 'staging') || 'preview' }} (push) Has been cancelled
End to end tests (examples) / End to end tests (examples) (push) Has been cancelled
Publish SDK packages / Publish SDK packages (push) Has been cancelled
Publish SDK packages / Tell user released version (push) Has been cancelled
Publish SDK packages / Publishes code templates to separate repositories (push) Has been cancelled
Publish SDK packages / Deploys the template demos with the new SDK version (push) Has been cancelled
This commit is contained in:
238
skills/update-release-notes/SKILL.md
Normal file
238
skills/update-release-notes/SKILL.md
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
name: update-release-notes
|
||||
description: Update the release notes file at `apps/docs/content/releases/next.mdx` in the tldraw/tldraw repo based on PRs since the previous release, or archive `next.mdx` to a versioned file when a new version is published.
|
||||
allowed-tools: Bash(git:*), Bash(gh:*)
|
||||
---
|
||||
|
||||
# Update release notes
|
||||
|
||||
This skill handles the operational tasks of maintaining release notes in the **tldraw/tldraw** repo: adding new PR entries to `next.mdx` and archiving releases when a new version is published.
|
||||
|
||||
This skill runs from the tldraw-internal repo but operates on a local clone of tldraw/tldraw.
|
||||
|
||||
## Setup
|
||||
|
||||
Before starting, clone the tldraw repo:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tldraw/tldraw.git /tmp/tldraw
|
||||
```
|
||||
|
||||
All scripts take the repo path as their first argument. Run script commands from the repository root by replacing `<skill-dir>` with the directory that contains this `SKILL.md`. All file edits happen in this clone. At the end, commit and push from the clone.
|
||||
|
||||
## Release cycle
|
||||
|
||||
The SDK releases every four weeks. One week before launch, the SDK is frozen and a `production` branch is cut from `main`. During release week, only hotfixes are cherry-picked to `production`.
|
||||
|
||||
The release notes in `next.mdx` must ultimately reflect what ships on the `production` branch. However, during the ~3 development weeks before the freeze, we build up release notes from `main` so they're not empty. The status script detects which phase we're in based on the date of the last minor release:
|
||||
|
||||
- **Development weeks** (`source: "main"`, <3 weeks since last minor release): PRs are gathered from `main` since the last release tag. These are preliminary — some may not ship if they're reverted or if production cherry-picks differ.
|
||||
- **Freeze week** (`source: "production"`, 3+ weeks since last minor release): PRs are gathered from the `production` branch using `git cherry` against the previous release branch. Only PRs that landed before the freeze or were hotfixed onto production are included. Entries accumulated from `main` that aren't on production will be pruned.
|
||||
|
||||
`next.mdx` itself also has to be **on `production`** before the release is published, not just describe it. `prepack.ts` generates the `RELEASE_NOTES.md` that ships inside the `tldraw` npm package from the checkout being published, using `next.mdx` as the entry for the version being released. `production` only moves by hotfix during the freeze, so a release-notes PR merged to `main` reaches it only if it also carries the `dotcom-hotfix-please` label (see step 10). The check before publishing is an empty diff:
|
||||
|
||||
```bash
|
||||
git diff --stat origin/production origin/main -- apps/docs/content/releases/next.mdx
|
||||
```
|
||||
|
||||
## Process
|
||||
|
||||
### 1. Check status
|
||||
|
||||
Run the status script to determine versions and diff branch:
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/get-changelog-status.sh /tmp/tldraw
|
||||
```
|
||||
|
||||
This returns:
|
||||
|
||||
- `source` - either `"main"` (development weeks) or `"production"` (release week)
|
||||
- `diff_branch` (e.g., `v4.3.x`) - the branch to diff against (used when `source` is `"production"`)
|
||||
- `last_tag` (e.g., `v4.4.0`) - the last release tag (used when `source` is `"main"`)
|
||||
- `latest_release` - the most recent published release
|
||||
- Whether archival is needed
|
||||
|
||||
### 2. Handle archival (if needed)
|
||||
|
||||
If a new release was published since `last_version` in `next.mdx`:
|
||||
|
||||
1. Copy `next.mdx` content to `vX.Y.0.mdx` (e.g., `v4.3.0.mdx`)
|
||||
2. Update the frontmatter in the new file:
|
||||
- Set `title` to the version
|
||||
- Update dates
|
||||
- Add the GitHub release link after frontmatter
|
||||
3. Add the new version to the releases index at `apps/docs/content/getting-started/releases.mdx`:
|
||||
- Add a line at the top of the appropriate `## v{major}.x` section
|
||||
- Format: `- [vX.Y](/releases/vX.Y.0) - Brief description`
|
||||
- The description should be a short summary of the release highlights from the intro paragraph (3-5 key features, comma-separated)
|
||||
- If a new major version section is needed, create it above the previous one
|
||||
4. Reset `next.mdx`:
|
||||
- Update `last_version` field to the new release
|
||||
- Clear the content sections
|
||||
- Keep the file structure for accumulating new changes
|
||||
|
||||
### 3. Find new PRs
|
||||
|
||||
The approach depends on the `source` field from step 1.
|
||||
|
||||
**If `source` is `"production"` (release week):**
|
||||
|
||||
Get PR numbers on the production branch since the previous release:
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/get-new-prs.sh /tmp/tldraw <diff_branch>
|
||||
```
|
||||
|
||||
This uses `git cherry` (same approach as `extract-draft-changelog.tsx`) to compare patches between `origin/production` and `origin/<diff_branch>`. Unlike `git log`, `git cherry` correctly handles cherry-picked hotfix commits by comparing patch content rather than commit hashes.
|
||||
|
||||
**If `source` is `"main"` (development weeks):**
|
||||
|
||||
Get PR numbers on main since the last release tag:
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/get-new-prs-from-main.sh /tmp/tldraw <last_tag>
|
||||
```
|
||||
|
||||
This uses `git cherry` to compare patches between `origin/main` and the release tag, excluding commits that were cherry-picked to production and already shipped in the release.
|
||||
|
||||
**Both scripts output:**
|
||||
|
||||
- **stdout**: PR numbers (one per line) extracted from commit messages
|
||||
- **stderr**: Commits without PR numbers (typically hotfixes or direct commits)
|
||||
|
||||
### 4. Resolve hotfix commits
|
||||
|
||||
Check stderr from step 3 for any `[HOTFIX]` commits without PR numbers. For each SDK-relevant one (skip dotcom-only), find the corresponding PR:
|
||||
|
||||
```bash
|
||||
gh pr list -R tldraw/tldraw --state merged --search "<hotfix title keywords>" --json number,title --jq '.[]'
|
||||
```
|
||||
|
||||
Also check `sdk-hotfix-please` labeled PRs, but verify they actually landed on production — this label is also used for patch releases on the previous release branch (e.g., v4.3.x):
|
||||
|
||||
```bash
|
||||
gh pr list -R tldraw/tldraw --label "sdk-hotfix-please" --state merged --limit 20 --json number,title,mergedAt
|
||||
```
|
||||
|
||||
Add any matched PR numbers to the production PR list from step 3.
|
||||
|
||||
### 5. Fetch PR details
|
||||
|
||||
Batch fetch all PR information:
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/fetch-pr-batch.sh <pr1> <pr2> ...
|
||||
```
|
||||
|
||||
### 6. Remove stale entries
|
||||
|
||||
Compare the full PR list (from steps 3 + 4) against the PR numbers already referenced in `next.mdx`. Remove any entries from `next.mdx` whose PRs are **not** in the current set.
|
||||
|
||||
- **When `source` is `"production"`**: This prunes entries that were accumulated from `main` during development but didn't make it to production. These are PRs that landed on `main` after the production branch was cut and will not ship in this release.
|
||||
- **When `source` is `"main"`**: This removes entries for PRs that were reverted or otherwise no longer on main. Pruning is less aggressive since the full set isn't finalized until production is cut.
|
||||
|
||||
When removing an entry, also check whether it was referenced in a featured "What's new" section and remove that section too. Update the intro paragraph if it mentions a removed feature.
|
||||
|
||||
### 7. Add new entries
|
||||
|
||||
For each PR not already in `next.mdx`:
|
||||
|
||||
1. Check PR labels and body against the style guide's categorization rules
|
||||
2. Skip PRs that should be omitted (see style guide)
|
||||
3. Skip PRs that fix bugs introduced in the same release cycle (i.e., bugs caused by other PRs already in `next.mdx` that were not in the previous release)
|
||||
4. If a PR is a revert, also remove the original reverted PR's entry from `next.mdx` if present
|
||||
5. Add entries to the appropriate section
|
||||
6. Promote significant changes to featured sections when warranted
|
||||
|
||||
### 8. Verify
|
||||
|
||||
Check that:
|
||||
|
||||
- Every PR referenced in `next.mdx` is in the current PR set (from `main` or `production` depending on `source`)
|
||||
- PR links are correct
|
||||
- Community contributors are credited
|
||||
- Sections are in the correct order
|
||||
- Breaking changes are marked with 💥
|
||||
- Deprecations are marked with 🔜
|
||||
|
||||
**Note**: When `source` is `"main"`, the entries are preliminary. Some may be pruned later when the production branch is cut and the source switches to `"production"`.
|
||||
|
||||
### 9. Update the draft GitHub release
|
||||
|
||||
Keep the draft GitHub release for the next minor in sync with `next.mdx`. `publish-new.ts` finds the draft by name (`vX.Y.0`) and publishes its body verbatim at release time, so mirroring `next.mdx` here means the GitHub release and the docs release page always match.
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/update-draft-release.sh /tmp/tldraw
|
||||
```
|
||||
|
||||
This script:
|
||||
|
||||
- Derives the target version from `last_version` in `next.mdx` (bump minor, patch `0` — e.g. `v5.2.0` → `v5.3.0`).
|
||||
- Uses the `next.mdx` body (frontmatter stripped) as the release body.
|
||||
- Creates the draft if none exists, or edits the existing draft named `vX.Y.0`. It refuses to touch an already-published release, and refuses to write an empty body.
|
||||
|
||||
**Major releases / version overrides:** the minor bump above is the default because nearly every release is a minor. `publish-new.ts` looks up the draft by the version it resolves — for `--bump major` (or an override) that comes from NPM, not a minor bump, so a hardcoded minor would leave the draft misnamed and `getDraftRelease` would fail. When the next release is a major or otherwise not the next minor, pass the target explicitly so the draft name matches:
|
||||
|
||||
```bash
|
||||
<skill-dir>/scripts/update-draft-release.sh /tmp/tldraw v6.0.0
|
||||
```
|
||||
|
||||
Run this after every `next.mdx` update, including the release-week runs. On the second (post-publish) run the previous minor's draft has already been published, so this creates a fresh draft for the new next minor derived from the reset `last_version`.
|
||||
|
||||
### 10. Push changes
|
||||
|
||||
After editing `next.mdx` (and any archive files), commit and push from the tldraw clone. Do not add `[skip ci]` to the branch commit: required pull request checks will never start when the marker is present. The PR title below adds the marker to the eventual squash-merge commit instead.
|
||||
|
||||
```bash
|
||||
cd /tmp/tldraw
|
||||
BRANCH="update-release-notes-$(date -u +%Y%m%d-%H%M)"
|
||||
git checkout -b "$BRANCH"
|
||||
git add apps/docs/content/releases/
|
||||
git commit -m "docs(releases): update release notes"
|
||||
git push -u origin "$BRANCH"
|
||||
```
|
||||
|
||||
Then create the PR using the `../pr/SKILL.md` workflow and the standards in `../write-pr/SKILL.md`:
|
||||
|
||||
- **Label**: add `docs-hotfix-please` (`gh pr edit <number> --add-label docs-hotfix-please`, or `--label docs-hotfix-please` on `gh pr create`). Without it, merging to `main` never cherry-picks the notes onto the current release branch (`vX.Y.x`), so the update never reaches tldraw.dev — which is the whole point of the run.
|
||||
- **Freeze-week label**: when `source` is `"production"`, also add `dotcom-hotfix-please`. That cherry-picks the merged PR onto `hotfixes` and from there onto `production`, which is where `prepack.ts` reads `next.mdx` from when the release is published (see "Release cycle"). `docs-hotfix-please` alone never touches `production`. After the merge, confirm the hotfix landed with the diff command from "Release cycle"; if the dotcom hotfix bot fails, cherry-pick the squash commit onto a branch from `hotfixes` by hand, strip any `[skip ci]` marker from the commit message, and open a `[HOTFIX]` PR against `hotfixes`.
|
||||
- **Title**: `docs(releases): update release notes [skip ci]` — the `[skip ci]` must be in the PR title because GitHub's squash-merge uses the PR title as the merge commit subject, and that merge commit is what needs to skip CI. **Squash-merge** the PR (do not rebase- or create-a-merge-commit) so exactly one `[skip ci]` commit lands on the base branch.
|
||||
- **Change type**: `other`
|
||||
- **Test plan**: Remove the numbered list (no manual testing steps). Untick both unit tests and end to end tests.
|
||||
- **Release notes**: Omit this section (internal docs work with no user-facing impact)
|
||||
- **Description paragraph**: Start with "In order to..." and mention the source (`main` or `production`) and what was updated (new entries added, stale entries pruned, archival performed, draft GitHub release synced, etc.)
|
||||
- Include a **Code changes** table with a `Documentation` row
|
||||
|
||||
**Why `[skip ci]` is only in the PR title**: branch commits need to run the required pull request checks. GitHub's squash merge uses the PR title for the merge commit, so the marker skips push workflows when the notes-only change lands on `main`.
|
||||
|
||||
`[skip ci]` does not interfere with the `docs-hotfix-please` label: `trigger-sdk-hotfix.yml` runs on `pull_request_target`, which GitHub's skip-ci markers do not affect, and the hotfix script strips skip-ci markers from the cherry-picked commit so the release-branch push still triggers the publish workflow. The trigger fires when a labeled PR is merged; to re-trigger it later, remove and re-add the label on the merged PR. The same applies to `dotcom-hotfix-please` once #10629 is fixed; until then its bot keeps the marker and the hotfix PR it opens never gets its checks.
|
||||
|
||||
Never write the literal `[skip ci]` string anywhere else in a PR body. GitHub reads the marker from the whole squash-merge commit message, and the PR body becomes that commit's body.
|
||||
|
||||
## The `last_version` field
|
||||
|
||||
The `next.mdx` frontmatter includes a `last_version` field that tracks the most recent published release. This determines which PRs are "new" and need to be added.
|
||||
|
||||
## SDK release workflow
|
||||
|
||||
During an SDK release, this skill is run **twice** to get the docs site into its post-release state:
|
||||
|
||||
1. **First run** — update `next.mdx` with all PRs that will ship in the release (source is `"production"` during freeze week). This ensures the release notes are complete before publishing. The draft-release step (step 9) syncs the draft `vX.Y.0` GitHub release from this `next.mdx`, so `publish-new.ts` has the finished notes ready to publish. The PR carries both `docs-hotfix-please` and `dotcom-hotfix-please` (step 10) so the file reaches `v(X.Y-1).x` for the site and `production` for the package.
|
||||
2. **Gate** — before anyone publishes, `next.mdx` must be identical on `production` and `main` (the diff command in "Release cycle" prints nothing), and the draft `vX.Y.0` release must match `next.mdx`. If a later edit to `next.mdx` landed on `main` without the dotcom hotfix, or after the last draft sync, fix that first.
|
||||
3. **Publish the release to NPM** — this happens outside of this skill. `publish-new.ts` publishes the draft release created in step 1, and `prepack.ts` bakes `production`'s `next.mdx` into the `tldraw` package as that version's `RELEASE_NOTES.md` entry. The publish pushes the version bump to `main`, not `production`.
|
||||
4. **Second run** — now that the release is published, the status script will detect `needs_archive: true`. This workflow runs on `production` pushes, and the publish doesn't make one, so the second run happens on the next `production` push (normally the post-launch dotcom release from `main`), or when someone dispatches `update-release-notes.yml` by hand. This run:
|
||||
- Archives `next.mdx` to a versioned file (e.g., `v4.5.0.mdx`)
|
||||
- Adds the new version to the releases index (`releases.mdx`)
|
||||
- Resets `next.mdx` with the new `last_version`
|
||||
- Finds PRs that landed on `main` during the freeze (since the release tag) and adds them to the fresh `next.mdx`
|
||||
- Syncs a fresh draft for the new next minor (step 9), since the previous draft is now published
|
||||
- Needs `docs-hotfix-please` (not `dotcom-hotfix-please`: `production` no longer matters for the notes once the package is out) so it lands on the new `vX.Y.x` branch and republishes the site
|
||||
|
||||
After the second run, the docs site reflects the published release and `next.mdx` already has a head start on the next cycle's changes.
|
||||
|
||||
## References
|
||||
|
||||
- **Style guide**: See `../shared/release-notes-guide.md` for guidance on what a release notes article should contain and how to format it.
|
||||
- **Writing guide**: See `../../VOICE.md` for voice and style conventions.
|
||||
- **Scripts**: See `scripts/` for automation helpers, including `update-draft-release.sh` for syncing the draft GitHub release
|
||||
58
skills/update-release-notes/scripts/fetch-pr-batch.sh
Executable file
58
skills/update-release-notes/scripts/fetch-pr-batch.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
# Batch fetch PR details for changelog categorization
|
||||
#
|
||||
# Usage: ./fetch-pr-batch.sh <pr1> <pr2> ...
|
||||
# or: echo "1234 5678" | ./fetch-pr-batch.sh
|
||||
# Output: JSON array of PR details
|
||||
|
||||
set -e
|
||||
|
||||
# Collect PR numbers from arguments or stdin
|
||||
prs=()
|
||||
if [ $# -gt 0 ]; then
|
||||
prs=("$@")
|
||||
else
|
||||
while read -r line; do
|
||||
for pr in $line; do
|
||||
prs+=("$pr")
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#prs[@]} -eq 0 ]; then
|
||||
echo "Usage: $0 <pr1> <pr2> ..." >&2
|
||||
echo " or: echo '1234 5678' | $0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Build GraphQL query for batch fetching
|
||||
# This is much faster than individual gh pr view calls
|
||||
query='query($owner: String!, $repo: String!) {'
|
||||
for i in "${!prs[@]}"; do
|
||||
pr="${prs[$i]}"
|
||||
query+="
|
||||
pr$i: repository(owner: \$owner, name: \$repo) {
|
||||
pullRequest(number: $pr) {
|
||||
number
|
||||
title
|
||||
author { login }
|
||||
mergedAt
|
||||
labels(first: 10) { nodes { name } }
|
||||
body
|
||||
}
|
||||
}"
|
||||
done
|
||||
query+='}'
|
||||
|
||||
# Execute query
|
||||
result=$(gh api graphql -f query="$query" -f owner="tldraw" -f repo="tldraw" 2>/dev/null)
|
||||
|
||||
# Transform into cleaner JSON array
|
||||
echo "$result" | jq '[.data | to_entries[] | .value.pullRequest | select(. != null) | {
|
||||
number: .number,
|
||||
title: .title,
|
||||
author: .author.login,
|
||||
merged_at: .mergedAt,
|
||||
labels: [.labels.nodes[].name],
|
||||
body: .body
|
||||
}] | sort_by(.number) | reverse'
|
||||
116
skills/update-release-notes/scripts/get-changelog-status.sh
Executable file
116
skills/update-release-notes/scripts/get-changelog-status.sh
Executable file
@@ -0,0 +1,116 @@
|
||||
#!/bin/bash
|
||||
# Get changelog status: versions, whether archival is needed, and the diff branch
|
||||
#
|
||||
# Usage: ./get-changelog-status.sh <tldraw-repo-dir>
|
||||
# Output: JSON with version info and status
|
||||
#
|
||||
# Requires a local clone of tldraw/tldraw.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <tldraw-repo-dir>" >&2
|
||||
echo "Example: $0 /tmp/tldraw" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TLDRAW_REPO="$1"
|
||||
RELEASES_DIR="$TLDRAW_REPO/apps/docs/content/releases"
|
||||
NEXT_FILE="$RELEASES_DIR/next.mdx"
|
||||
|
||||
# Extract last_version from next.mdx frontmatter (if present)
|
||||
last_version=""
|
||||
if [ -f "$NEXT_FILE" ]; then
|
||||
last_version=$(grep -E "^last_version:" "$NEXT_FILE" 2>/dev/null | sed "s/last_version: *//" | tr -d "'\"\n" || echo "")
|
||||
fi
|
||||
|
||||
# Get the latest published release from GitHub
|
||||
latest_release=$(gh release list -R tldraw/tldraw --exclude-drafts --limit 1 --json tagName,publishedAt -q '.[0]' 2>/dev/null || echo '{}')
|
||||
latest_tag=$(echo "$latest_release" | jq -r '.tagName // empty')
|
||||
latest_date=$(echo "$latest_release" | jq -r '.publishedAt // empty')
|
||||
|
||||
# Determine if we need to archive (latest release is newer than last_version)
|
||||
needs_archive="false"
|
||||
if [ -n "$latest_tag" ] && [ -n "$last_version" ]; then
|
||||
if [ "$latest_tag" != "$last_version" ]; then
|
||||
needs_archive="true"
|
||||
fi
|
||||
elif [ -n "$latest_tag" ] && [ -z "$last_version" ]; then
|
||||
# No last_version set, assume we might need to check
|
||||
needs_archive="unknown"
|
||||
fi
|
||||
|
||||
# Determine the release branch to diff against
|
||||
# e.g., v4.3.0 -> v4.3.x, or if latest is v4.3.1 -> v4.3.x
|
||||
diff_branch=""
|
||||
if [ -n "$latest_tag" ]; then
|
||||
# Extract major.minor from tag (v4.3.0 -> 4.3)
|
||||
version_nums=$(echo "$latest_tag" | sed 's/^v//' | cut -d. -f1,2)
|
||||
diff_branch="v${version_nums}.x"
|
||||
fi
|
||||
|
||||
# Check if the archive file already exists
|
||||
archive_exists="false"
|
||||
if [ -n "$latest_tag" ]; then
|
||||
archive_file="$RELEASES_DIR/${latest_tag}.mdx"
|
||||
if [ -f "$archive_file" ]; then
|
||||
archive_exists="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Check if next.mdx has content (more than just frontmatter)
|
||||
next_has_content="false"
|
||||
if [ -f "$NEXT_FILE" ]; then
|
||||
# Count lines after frontmatter (after second ---)
|
||||
content_lines=$(awk '/^---$/{c++;next}c==2' "$NEXT_FILE" | grep -v '^[[:space:]]*$' | wc -l | tr -d ' ')
|
||||
if [ "$content_lines" -gt 0 ]; then
|
||||
next_has_content="true"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Determine source based on weeks since the last minor/major release.
|
||||
# The SDK releases every 4 weeks. During the first 3 weeks (development),
|
||||
# we gather PRs from main. At 3+ weeks (freeze week), we switch to production.
|
||||
#
|
||||
# Find the last minor or major release (skip patches like v4.3.1, v4.3.2)
|
||||
source="main"
|
||||
last_minor_date=""
|
||||
if [ -n "$latest_tag" ]; then
|
||||
# Get the .0 release for the current minor version (e.g., v4.4.0)
|
||||
minor_tag=$(echo "$latest_tag" | sed 's/^v//' | cut -d. -f1,2)
|
||||
minor_release_tag="v${minor_tag}.0"
|
||||
# Get the publish date of that .0 release
|
||||
last_minor_date=$(gh release view "$minor_release_tag" -R tldraw/tldraw --json publishedAt -q '.publishedAt' 2>/dev/null || echo "")
|
||||
if [ -n "$last_minor_date" ]; then
|
||||
# Calculate weeks since the minor release
|
||||
release_epoch=$(date -jf "%Y-%m-%dT%H:%M:%SZ" "$last_minor_date" "+%s" 2>/dev/null || date -d "$last_minor_date" "+%s" 2>/dev/null || echo "")
|
||||
now_epoch=$(date "+%s")
|
||||
if [ -n "$release_epoch" ]; then
|
||||
days_since=$(( (now_epoch - release_epoch) / 86400 ))
|
||||
weeks_since=$(( days_since / 7 ))
|
||||
if [ "$weeks_since" -ge 3 ]; then
|
||||
source="production"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# The last tag is the latest release tag (used by get-new-prs-from-main.sh)
|
||||
last_tag="${latest_tag}"
|
||||
|
||||
# Output JSON
|
||||
cat <<EOF
|
||||
{
|
||||
"last_version": "${last_version}",
|
||||
"latest_release": {
|
||||
"tag": "${latest_tag}",
|
||||
"published_at": "${latest_date}"
|
||||
},
|
||||
"diff_branch": "${diff_branch}",
|
||||
"last_tag": "${last_tag}",
|
||||
"source": "${source}",
|
||||
"needs_archive": ${needs_archive},
|
||||
"archive_exists": ${archive_exists},
|
||||
"next_has_content": ${next_has_content}
|
||||
}
|
||||
EOF
|
||||
88
skills/update-release-notes/scripts/get-new-prs-from-main.sh
Executable file
88
skills/update-release-notes/scripts/get-new-prs-from-main.sh
Executable file
@@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
# Find PR numbers on the main branch since a release tag.
|
||||
# Uses `git cherry` to compare patches between origin/main and the
|
||||
# release tag, excluding commits that were cherry-picked to production
|
||||
# and already shipped in the release.
|
||||
#
|
||||
# Usage: ./get-new-prs-from-main.sh <tldraw-repo-dir> <release-tag>
|
||||
# Example: ./get-new-prs-from-main.sh /tmp/tldraw v4.4.0
|
||||
#
|
||||
# Output:
|
||||
# PR numbers (one per line) on stdout, then any commits
|
||||
# without PR numbers (hash + title) on stderr.
|
||||
#
|
||||
# Requires a local clone of tldraw/tldraw.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo "Usage: $0 <tldraw-repo-dir> <release-tag>" >&2
|
||||
echo "Example: $0 /tmp/tldraw v4.4.0" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TLDRAW_REPO="$1"
|
||||
RELEASE_TAG="$2"
|
||||
|
||||
cd "$TLDRAW_REPO"
|
||||
|
||||
# Fetch to ensure we have latest refs
|
||||
git fetch origin main --quiet 2>/dev/null || true
|
||||
|
||||
# Verify the tag exists
|
||||
if ! git rev-parse "$RELEASE_TAG" >/dev/null 2>&1; then
|
||||
echo "Error: tag '$RELEASE_TAG' not found" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# The release tag lives on the production branch, which has a separate
|
||||
# history from main (deploy commits, hotfixes, version bumps). Many
|
||||
# commits on main after the branch point were cherry-picked to production
|
||||
# and shipped in the release. We use `git cherry` to compare patches and
|
||||
# exclude commits that are already in the release — just like
|
||||
# get-new-prs.sh does for production.
|
||||
#
|
||||
# `git cherry <release-tag> origin/main` marks commits unique to main
|
||||
# with '+' and those already in the release with '-'.
|
||||
commit_hashes=$(git cherry "$RELEASE_TAG" origin/main 2>/dev/null \
|
||||
| grep '^+' \
|
||||
| cut -d' ' -f2)
|
||||
|
||||
pr_numbers=()
|
||||
unmatched=()
|
||||
|
||||
for hash in $commit_hashes; do
|
||||
# Get the first line of the commit message
|
||||
title=$(git log -1 --pretty=format:%s "$hash" 2>/dev/null)
|
||||
|
||||
# Skip [skip ci] commits (version bumps, deploys)
|
||||
if echo "$title" | grep -q '\[skip ci\]'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip deploy commits
|
||||
if echo "$title" | grep -q '^Deploy from'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract PR number from commit message (format: "... (#1234)")
|
||||
pr=$(echo "$title" | grep -oE '#[0-9]+' | head -1 | sed 's/#//')
|
||||
|
||||
if [ -n "$pr" ]; then
|
||||
pr_numbers+=("$pr")
|
||||
else
|
||||
unmatched+=("$hash $title")
|
||||
fi
|
||||
done
|
||||
|
||||
# Output unique PR numbers, sorted descending
|
||||
printf '%s\n' "${pr_numbers[@]}" | sort -rn | uniq
|
||||
|
||||
# Output unmatched commits to stderr
|
||||
if [ ${#unmatched[@]} -gt 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Commits without PR numbers:" >&2
|
||||
for line in "${unmatched[@]}"; do
|
||||
echo " $line" >&2
|
||||
done
|
||||
fi
|
||||
76
skills/update-release-notes/scripts/get-new-prs.sh
Executable file
76
skills/update-release-notes/scripts/get-new-prs.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/bin/bash
|
||||
# Find PR numbers on the production branch since a release branch.
|
||||
# Uses `git cherry` (like extract-draft-changelog.tsx) to correctly
|
||||
# handle cherry-picked hotfix commits.
|
||||
#
|
||||
# Usage: ./get-new-prs.sh <tldraw-repo-dir> <release-branch>
|
||||
# Example: ./get-new-prs.sh /tmp/tldraw v4.2.x
|
||||
#
|
||||
# Output:
|
||||
# PR numbers (one per line), then any commits
|
||||
# without PR numbers (hash + title) on stderr.
|
||||
#
|
||||
# Requires a local clone of tldraw/tldraw.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ] || [ -z "$2" ]; then
|
||||
echo "Usage: $0 <tldraw-repo-dir> <release-branch>" >&2
|
||||
echo "Example: $0 /tmp/tldraw v4.2.x" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TLDRAW_REPO="$1"
|
||||
RELEASE_BRANCH="$2"
|
||||
|
||||
cd "$TLDRAW_REPO"
|
||||
|
||||
# Fetch to ensure we have latest refs
|
||||
git fetch origin production "$RELEASE_BRANCH" --quiet 2>/dev/null || true
|
||||
|
||||
# Use git cherry to find commits on production that aren't on the
|
||||
# release branch. git cherry compares patches (not hashes), so it
|
||||
# correctly handles cherry-picks and hotfixes.
|
||||
# Lines starting with '+' are unique to production.
|
||||
commit_hashes=$(git cherry "origin/$RELEASE_BRANCH" origin/production 2>/dev/null \
|
||||
| grep '^+' \
|
||||
| cut -d' ' -f2)
|
||||
|
||||
pr_numbers=()
|
||||
unmatched=()
|
||||
|
||||
for hash in $commit_hashes; do
|
||||
# Get the first line of the commit message
|
||||
title=$(git log -1 --pretty=format:%s "$hash" 2>/dev/null)
|
||||
|
||||
# Skip [skip ci] commits (version bumps, deploys)
|
||||
if echo "$title" | grep -q '\[skip ci\]'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Skip deploy commits
|
||||
if echo "$title" | grep -q '^Deploy from'; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Extract PR number from commit message (format: "... (#1234)")
|
||||
pr=$(echo "$title" | grep -oE '#[0-9]+' | head -1 | sed 's/#//')
|
||||
|
||||
if [ -n "$pr" ]; then
|
||||
pr_numbers+=("$pr")
|
||||
else
|
||||
unmatched+=("$hash $title")
|
||||
fi
|
||||
done
|
||||
|
||||
# Output unique PR numbers, sorted descending
|
||||
printf '%s\n' "${pr_numbers[@]}" | sort -rn | uniq
|
||||
|
||||
# Output unmatched commits (hotfixes without PR numbers) to stderr
|
||||
if [ ${#unmatched[@]} -gt 0 ]; then
|
||||
echo "" >&2
|
||||
echo "Commits without PR numbers (likely hotfixes):" >&2
|
||||
for line in "${unmatched[@]}"; do
|
||||
echo " $line" >&2
|
||||
done
|
||||
fi
|
||||
84
skills/update-release-notes/scripts/update-draft-release.sh
Executable file
84
skills/update-release-notes/scripts/update-draft-release.sh
Executable file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
# Update (or create) the draft GitHub release for the next release using the
|
||||
# current next.mdx content. publish-new.ts finds this draft by name (vX.Y.0) and
|
||||
# publishes its body verbatim at release time, so keeping it in sync with next.mdx
|
||||
# means the GitHub release and the docs release page always match.
|
||||
#
|
||||
# Usage: ./update-draft-release.sh <tldraw-repo-dir> [next-version]
|
||||
#
|
||||
# next-version overrides the target tag (with or without a leading `v`). Pass it
|
||||
# for a major release or any override, so the draft name matches the version
|
||||
# publish-new.ts will resolve. When omitted, the target defaults to the next
|
||||
# minor of last_version — the common case.
|
||||
#
|
||||
# Requires a local clone of tldraw/tldraw and an authenticated gh CLI.
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$1" ]; then
|
||||
echo "Usage: $0 <tldraw-repo-dir> [next-version]" >&2
|
||||
echo "Example: $0 /tmp/tldraw # minor bump of last_version" >&2
|
||||
echo "Example: $0 /tmp/tldraw v6.0.0 # explicit target (major/override)" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TLDRAW_REPO="$1"
|
||||
NEXT_FILE="$TLDRAW_REPO/apps/docs/content/releases/next.mdx"
|
||||
|
||||
if [ ! -f "$NEXT_FILE" ]; then
|
||||
echo "next.mdx not found at $NEXT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -n "$2" ]; then
|
||||
# Explicit target — normalize to a leading `v`. Use this for major releases or
|
||||
# overrides, where publish-new.ts resolves the next version from NPM rather than
|
||||
# a minor bump (e.g. --bump major -> v6.0.0).
|
||||
next_tag="v$(echo "$2" | sed 's/^v//')"
|
||||
else
|
||||
# The draft tracks the next minor release. Derive it from last_version (the most
|
||||
# recent published release recorded in next.mdx frontmatter): bump minor, patch 0.
|
||||
# e.g. last_version v5.2.0 -> draft v5.3.0.
|
||||
last_version=$(grep -E "^last_version:" "$NEXT_FILE" | head -1 | sed "s/last_version: *//" | tr -d "'\"[:space:]")
|
||||
if [ -z "$last_version" ]; then
|
||||
echo "Could not read last_version from $NEXT_FILE" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
major=$(echo "$last_version" | sed 's/^v//' | cut -d. -f1)
|
||||
minor=$(echo "$last_version" | sed 's/^v//' | cut -d. -f2)
|
||||
next_tag="v${major}.$((minor + 1)).0"
|
||||
fi
|
||||
|
||||
# Body = next.mdx with the frontmatter block stripped (everything after the second ---).
|
||||
body_file=$(mktemp)
|
||||
awk 'f{print} /^---$/{c++; if(c==2){f=1}}' "$NEXT_FILE" > "$body_file"
|
||||
|
||||
# Fail loudly if there is nothing to publish rather than pushing an empty draft.
|
||||
if ! grep -qE '[^[:space:]]' "$body_file"; then
|
||||
echo "next.mdx has no content below the frontmatter; refusing to write an empty draft." >&2
|
||||
rm -f "$body_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Draft release target: $next_tag" >&2
|
||||
|
||||
# Look up an existing release for this tag (finds drafts too).
|
||||
existing=$(gh release view "$next_tag" -R tldraw/tldraw --json isDraft 2>/dev/null || echo "")
|
||||
|
||||
if [ -n "$existing" ]; then
|
||||
is_draft=$(echo "$existing" | jq -r '.isDraft')
|
||||
if [ "$is_draft" != "true" ]; then
|
||||
echo "Release $next_tag already exists and is published; refusing to edit." >&2
|
||||
rm -f "$body_file"
|
||||
exit 1
|
||||
fi
|
||||
echo "Updating existing draft release $next_tag" >&2
|
||||
gh release edit "$next_tag" -R tldraw/tldraw --title "$next_tag" --notes-file "$body_file"
|
||||
else
|
||||
echo "Creating new draft release $next_tag" >&2
|
||||
gh release create "$next_tag" -R tldraw/tldraw --draft --title "$next_tag" --notes-file "$body_file"
|
||||
fi
|
||||
|
||||
rm -f "$body_file"
|
||||
echo "Done. Draft release $next_tag is in sync with next.mdx." >&2
|
||||
Reference in New Issue
Block a user