release-process
GitHub管理 Markdown Preview 发布流程,包括版本递增、PR 创建、标签打制及回滚。涵盖 Amore 分发配置(代码签名、公证)及 What's New 窗口更新。
Trigger Scenarios
Install
npx skills add pluk-inc/markdown-preview --skill release-process -g -y
SKILL.md
Frontmatter
{
"name": "release-process",
"description": "Prepare complete release PRs, publish, tag, and roll back Markdown Preview releases — branch\/PR naming, exactly what scripts\/release.sh and scripts\/rollback-release.sh do (including that release.sh publishes live), and the already-wired Amore distribution config (codesign identity, notary profile, EdDSA key). Use when the user asks to create a release PR, release, ship, cut a version, bump the version, tag a release, roll back or unpublish a release, or asks about this project's Amore-specific config (codesign identity, notary keychain profile, EdDSA key, custom domain)."
}
Release pipeline
Branch and PR naming
Every release goes through a dedicated branch and PR — never push the version bump or changelog directly to main.
- Branch name:
release/X.Y.Z— exactly the marketing version, novprefix, no build number, no suffix. Examples:release/0.0.10,release/1.2.0. Beta cuts userelease/X.Y.Z-betaN(e.g.release/0.1.0-beta1). - PR title:
Release X.Y.Z (N)whereNisCURRENT_PROJECT_VERSION. Example:Release 0.0.10 (14). This matches the commit messagescripts/release.shwrites for the version-bump commit, so the PR, the bump commit, and the eventual git tag all line up. For betas:Release X.Y.Z-betaN (build). - PR body: short Summary (version bump + changelog added), a "What's in X.Y.Z" section that mirrors the changelog bullets, and a Test plan.
- One PR per release. The branch contains only the bump (
Version.xcconfig, edited directly during PR preparation), the newCHANGELOG.mdentry, and — only when the user confirms an announcement — the What's New window update (see below). Keep unrelated changes out so the release diff stays auditable.
Create a release PR (prepare only)
A request to "create a release PR" includes both version fields and the changelog. It authorizes editing Version.xcconfig directly; do not defer the bump to publication or open a changelog-only release PR.
- Fetch latest
origin/mainand tags, and inspect existing release PRs before selecting the next version. Reuse an existing open release PR when appropriate. Start a new release branch from latest main; use an isolated worktree if the current checkout has unrelated work. - Use the requested version, or the next patch version in this project's existing release series when unspecified. Increment
CURRENT_PROJECT_VERSIONby one from the latest release metadata. Check open release PRs to avoid duplicating a version already being prepared. - Update both
MARKETING_VERSIONandCURRENT_PROJECT_VERSIONinVersion.xcconfig, and add the matchingCHANGELOG.mdentry usingchangelog-maintenance, including contributor credits. Then review the new changelog entry for What's New candidates and ask the user before announcing any (see What's New window below). Without a confirmed pick, leave the window files untouched. - Verify that the PR's complete diff includes the intended version/build values and matching changelog heading; the title must agree with those values. Check
git diff --check, unchanged historical notes, and that only these two release metadata files changed — plus the What's New files when the user confirmed an announcement, in which case build the app, run the policy tests, and look at the window as described below. Do not claim app tests were run for metadata-only validation. - Commit, push, and open a ready, non-draft PR titled
Release X.Y.Z (N). A release PR is incomplete if the version bump is missing, unless the user explicitly requested changelog-only work.
Do not run scripts/release.sh, including --draft, just to prepare the PR. Building, notarizing, uploading, tagging, and publishing are separate release execution steps; a release PR request alone does not request them.
What's New window
Updated readers see a What's New window once, over their first document window after the update (Help › What's New in Markdown Preview reopens it). It lives in md-preview/Features/WhatsNew/:
WhatsNewPolicy.swift—featuresVersionandfeaturesBuildname the release that introduced the listed features. A reader whose last recorded build is older thanfeaturesBuildsees the window; fresh installs never do. The window title (What's New in Markdown Preview <featuresVersion>) and the Release Notes button (https://github.com/pluk-inc/markdown-preview/releases/tag/v<featuresVersion>) both followfeaturesVersion.WhatsNewWindow.swift—WhatsNewFeature.currentis the list of features shown.
Always ask before announcing. While preparing a release PR, read the new CHANGELOG.md entry and pick the user-visible features worth announcing. Present them to the user as a proposal — title, description, SF Symbol, and any macOS version limit for each — and wait for explicit confirmation of which ones to use and their wording. Never add, drop, or reword an announcement without that confirmation. If the user picks none, leave WhatsNewPolicy.swift and WhatsNewWindow.swift untouched: the window then does not appear for this release, and readers who already saw the current announcement are not shown it again.
Choosing candidates:
- Announce what is new to readers of the previous release: new features and visible redesigns. Skip fixes, internal work, and anything that already existed (e.g. announce "wrap code blocks", not "copy code" when copy predates the release).
- Three to five features. Order them by how much a reader will notice them.
- A feature that only works on some systems is gated with
if #available(macOS NN, *)so readers on older systems do not see it, and its description says so ("On macOS 26 and later, …").
Wording format (match the existing entries):
- Title: sentence case, about two to five words, no trailing period — e.g. "Find any document fast", "Redesigned formatting bar", "Faster document opening".
- Description: one short sentence, or two very short ones, about 20 words at most. Say what the reader can do or will notice; name a shortcut when it is the way in (e.g. "Press ⇧⌘O and type part of a file name to open it from the current project.").
- Symbol: an SF Symbol that exists on macOS 15 (the window also runs there), drawn in the accent tint.
Once the user confirms:
- In
WhatsNewPolicy.swift, setfeaturesVersionto the newMARKETING_VERSIONandfeaturesBuildto the newCURRENT_PROJECT_VERSION. They must matchVersion.xcconfigin the same PR, or the title and release link name the wrong version and readers of the previous release may not see the window. - Replace the entries in
WhatsNewFeature.currentwith the confirmed features. - Add every new title and description to both
md-preview/en.lproj/Localizable.stringsandmd-preview/zh-Hans.lproj/Localizable.stringsunder/* What's New */, and remove strings that no entry uses any more (keep keys other UI still uses, such as "Search for Document"). Runplutil -linton both files. - Build the app and run
swift test --package-path tests/swift-tests --filter WhatsNewPolicyTests. - Check the window in the Debug build as an updating reader: run
defaults delete doc.md-preview.dev MarkdownPreview.whatsNewLastBuildanddefaults write doc.md-preview.dev MainSplitView.didSeedInitialState -bool true(a fresh Debug profile otherwise counts as a new install, which never sees the window), launch the built app with a document, and confirm the window lists the confirmed features. Before the release is published, the Release Notes link returns 404; that is expected.
How scripts/release.sh actually works
Read this before running it — the script ships the update, it doesn't just prepare a PR.
Preflight (checked in this order, before anything is changed):
- the working tree must be clean — commit everything, including the
CHANGELOG.mdentry, before running the script; - a
## [X.Y.Z]entry must already exist inCHANGELOG.mdfor the version being released (the script only validates and extracts it — it never writes the entry itself; that's thechangelog-maintenanceskill's job, see below); amoremust be logged in (amore whoami);- unless
--skip-githubor--draft, theghCLI must be installed and authenticated, and anoriginremote must exist — if there's noorigin, the script falls back to--skip-githubon its own with a warning.
jq (used to parse amore's JSON output) is checked later, right before the amore release call — after Version.xcconfig may already have been bumped and committed. If jq is missing when a version bump was needed, you're left with a local "Release X.Y.Z (N)" commit and no actual release; re-running the script once jq is installed is safe, since Version.xcconfig already matches and the sync step becomes a no-op.
What it does, once preflight passes:
- Resolves the version and build number (see the flag reference below).
- If
Version.xcconfigdoesn't already match the resolved version/build, updates it and commits directly on whatever branch is currently checked out, asRelease X.Y.Z (N). - Extracts that version's release notes from
CHANGELOG.md. - Runs
amore release— archives, signs, builds the DMG, notarizes, uploads, and — unless--draftwas passed — publishes the update to Amore's live appcast (check the currentSUFeedURLand Amore configuration as described in AGENTS.md's Release references). This is the actual "ship it" step. - Unless
--skip-githubor--draft: downloads the DMG, creates and pushes thevX.Y.Ztag, and creates the GitHub release (or, if a release for that tag already exists, uploads the DMG to it as an asset).
The script never pushes the branch itself and never opens or merges the PR. That remains a separate, manual step.
Publish a prepared release
When publication is requested, use the prepared release commit with a clean working tree and run ./scripts/release.sh. It reads the already-bumped version/build and matching changelog; the version sync becomes a no-op. If a version is supplied with --version and the file already matches, the script also retains the prepared build number.
Running the script before opening a PR publishes first unless --draft is passed. Follow the preparation workflow above when the request is for a review PR. If publication creates a tag before the release PR is merged, merge with a regular merge, not squash, to keep the tagged commit in main's ancestry.
Commands
./scripts/release.sh # release current Version.xcconfig
./scripts/release.sh --version 0.0.2 # bump marketing version (auto-bumps build)
./scripts/release.sh --version 0.0.2 --build 7 # bump marketing version, force build 7 instead of auto-bumping
./scripts/release.sh --build 7 # keep current marketing version, force build 7
./scripts/release.sh --beta # amore --beta + GH prerelease (still publishes live)
./scripts/release.sh --draft # amore --draft, no GH release — the only mode that doesn't publish
./scripts/release.sh --skip-github # local amore release only (still publishes live; skips tag + GH release)
Before running, add a CHANGELOG.md entry for the version being shipped and commit it — the script refuses to run on a dirty working tree. Always invoke the changelog-maintenance skill by reading its SKILL.md and following its instructions whenever the user asks you to write, generate, or update a changelog entry — do not draft freeform. The skill enforces the project's house format, the Keep-a-Changelog category split (Added / Changed / Fixed / Security), and contributor crediting (it always inspects git log and gh pr list for non-maintainer authors and adds a ### Contributors block with @username GitHub tags when any are found).
Entry shape:
## [0.0.2] – 2026-05-01
Short narrative summary.
- **Bullet for each change.**
- Bug fix bullet.
The en dash (–) between the version and the date matches the project's house style (used in the script's own help text and error messages) — follow it for consistency. The script's parser only checks that the line starts with ## [X.Y.Z]; the date and dash aren't validated, so this is a style convention, not something tooling enforces.
Source of truth: Version.xcconfig for the version numbers, CHANGELOG.md for the notes.
Rolling back a release
./scripts/rollback-release.sh --latest # unpublish latest, delete GH release+tag
./scripts/rollback-release.sh 0.0.2 # unpublish specific version
./scripts/rollback-release.sh 0.0.2 --delete # permanently delete on Amore
./scripts/rollback-release.sh 0.0.2 --keep-github # leave GitHub release in place
./scripts/rollback-release.sh --latest --yes # skip the confirmation prompt
Default is unpublish (reversible — flips published=false on Amore so it disappears from the appcast). Use --delete only when you're sure; it permanently removes the release. To re-publish after a non-destructive rollback: amore releases update <version> -b doc.md-preview --published true.
Amore configuration (already wired)
- Hosting: Amore-managed;
Info.plistcurrently useshttps://release.md-preview.app/v1/apps/doc.md-preview/appcast.xml. Verify the current Amore hosting configuration before releasing. - Codesign identity:
Developer ID Application: Mohamed Fauzaan (5P3TSMNV42) - Notary keychain profile:
md-preview-notary - EdDSA public key (in Info.plist
SUPublicEDKey):gIQjgqfjkIR+egQ4S1oBLxE/NCDxpXXGdZXSpn04VAY=— private key in login Keychain
To inspect or change: amore config show --bundle-id doc.md-preview / amore config set .... CLI lives at /usr/local/bin/amore.
Version History
-
ffff19f
Current 2026-10-19 03:26
新增对 What's New 窗口的支持,允许在确认后将更新内容纳入发布 PR。
-
719f2dd
2026-09-22 08:53
新增要求:准备发布PR时必须包含版本升级(Version.xcconfig),禁止仅提交Changelog而不更新版本号。
- e1c5c15 2026-09-09 03:18


