Skip to main content

release-and-publish

Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, fast-forwards `main` when the release rode a release PR, creates the annotated tag on the commit `main` now points at, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit stack — and in release PR mode, the pushed branch and open PR) is already complete — this skill is the post-wrapup merge + tag + publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.

설치로 이동

소스 정보

저장소
cyanheads/obsidian-mcp-server
최근 소스 활동
2026년 9월 19일 15:47
감지된 SKILL.md 언어
영어
스타
682
포크
103

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
release-and-publish
description
Ship a release end-to-end across every registry the project targets (npm, MCP Registry, GitHub Releases for `.mcpb` bundles, GHCR). Runs the final verification gate, fast-forwards `main` when the release rode a release PR, creates the annotated tag on the commit `main` now points at, pushes commits and tags, then publishes to each applicable destination. Assumes git wrapup (version bumps, changelog, commit stack — and in release PR mode, the pushed branch and open PR) is already complete — this skill is the post-wrapup merge + tag + publish workflow. Retries transient network failures on publish steps; halts with a partial-state report when retries are exhausted or the failure is terminal.
metadata
{"author":"cyanheads","version":"2.19","audience":"external","type":"workflow"}
## Preconditions This skill runs **after** git wrapup. By the time it's invoked: - Pre-wrapup verification is done (`field-test`, `security-pass`, `polish-docs-meta` as applicable) - `package.json` version is bumped - `changelog/<major.minor>.x/<version>.md` is authored - `CHANGELOG.md` is regenerated - README and every version-bearing file is in sync - Release commit (`chore(release): <version> — <theme>`) is at HEAD — or, in gated release PR mode, reachable from HEAD with only the review pass's commits above it - No tag exists yet — this skill creates it (step 4) - Working tree is clean - Release PR mode (see `git-wrapup`'s "Release PR mode"): HEAD is on `release/<version>`, the branch is pushed, the PR is open, and — in gated mode — the caller has confirmed the review pass is finished. Without that confirmation, halt: this skill never decides on its own that a review is done. If any are missing, halt and tell the user to finish wrapup first. Do not attempt to redo wrapup work from inside this skill. ## Failure Protocol Steps 5–9 are network-bound. For those, **retry transient failures up to 2 times** with short backoff (~5 s before the first retry, ~15 s before the second) before halting. All other steps halt on the first non-zero exit — they're deterministic and a second attempt won't change the outcome. ### Retry on transient patterns Match stderr (case-insensitive) against any of these — if matched, the failure is almost always a network blip; retry: - `integrity check failed` / `IntegrityCheckFailed` — corrupt tarball during download - `ECONNRESET` / `EAI_AGAIN` / `ETIMEDOUT` / `ENOTFOUND` — network layer - `connection reset` / `connection refused` — transport blip - `timed out` / `request timeout` — server or network timeout - HTTP `502` / `503` / `504` — transient registry error **Before retrying `docker buildx --push` (step 9)**, run `docker builder prune -f` to drop any cached corrupt layer. Skip this extra step for other retries. ### Never retry on idempotent-success signals These mean the step already succeeded on a prior run — treat as success and proceed to the next step: - npm (`bun publish`): `version already exists`, `You cannot publish over the previously published versions` - MCP Registry (`mcp-publisher publish`): `cannot publish duplicate version` - Tag (`git tag -a`): `already exists` with the tag pointing at HEAD — a prior run of this skill already created it; a tag pointing elsewhere is a conflict, not a success (see step 4) - GitHub Release (`gh release create`): `release already exists` — fall back to `gh release upload --clobber` (see step 8) ### Halt fallback If retries are exhausted, or the failure matches none of the transient patterns, halt and report: 1. Which step failed 2. The exact error output 3. Retry count attempted (0 for terminal errors, 2 for exhausted retries) 4. Which destinations already received the release (npm published? tag pushed? MCP Registry? GitHub Release with `.mcpb`? GHCR?) — the partial state across destinations The user fixes locally and re-invokes. On re-invocation, already-published destinations hit the idempotent-success signal and skip naturally — no manual step-skipping required. ## Steps ### 1. Sanity-check wrapup outputs Read `package.json` → capture `version`. Then use your git tools to verify: - **Working tree is clean** — no uncommitted changes - **The release commit is in the stack** — `git log -1 --format=%s` starts with `chore(release): <version>`, or, in gated release PR mode, `git log main..HEAD --format=%s` contains it with only the review pass's own commits above it (`release-pr-review` lands fixes as ordinary commits on top; the tag still goes on the tip). Any other commit above the release commit — new work, a second version — is a halt. - **Current branch** — `main`, or `release/<version>` in release PR mode. Anything else, halt. - **Release PR mode:** `gh pr view --json number,state,headRefOid` shows the PR `OPEN` with `headRefOid` equal to local HEAD. A mismatch means the branch has commits the PR doesn't (or the reverse) — halt and report both SHAs. Keep `number` and `headRefOid`: the merge check (step 3) and the tag body (step 4) need them after the checkout has moved to `main`. If the working tree is dirty or the release commit isn't in the stack as described, halt. ### 2. Run the verification gate All must succeed. Check `package.json` `scripts` for `test:all`; if absent, fall back to `test`: ```bash bun run devcheck bun run rebuild bun run test:all # or `bun run test` if no test:all bun run test:package # only if the script exists — NOT part of test:all ``` `test:package` is a separate gate wherever a project defines one: it verifies the public-export manifest against what the built subpaths actually export. A release that adds, removes, or renames an export passes `test:all` and fails here. Regenerate the manifest with the command the failure names rather than editing it by hand. Any non-zero exit → halt with the failing command's output. ### 3. Merge the release branch (release PR mode only) Skip when HEAD is on `main`. ```bash git switch main git merge --ff-only release/<version> git rev-parse HEAD # must equal the PR's headRefOid from step 1 ``` **Fast-forward only, locally — then tag (step 4) and push (step 5).** The stack lands on `main` byte-identical — same SHAs, same signatures, release commit at the tip. GitHub marks the PR merged on its own once the PR's head commit is reachable from `main`. Never merge through the GitHub UI or `gh pr merge`: squash destroys the stack, rebase-and-merge rewrites every SHA (stripping the signatures), and a merge commit breaks the linear history. If `--ff-only` refuses, `main` moved underneath the release branch. Halt and report — nothing has been created yet, and rebasing would change the SHAs the review pass approved and the PR records as its head, so that decision belongs to the caller. A `rev-parse` that disagrees with the PR's `headRefOid` after a successful fast-forward is the same halt. ### 4. Create the annotated tag The tag goes on HEAD. In release PR mode that is `main`'s tip after step 3 — the commit the PR's `headRefOid` names — so the tag is created on the branch it stays reachable from. ```bash cat > /tmp/tag-v<version>.md <<'TAG' <tag message> TAG git tag -a v<version> --cleanup=whitespace -F /tmp/tag-v<version>.md ``` If `v<version>` already exists and points at HEAD, a prior run created it — proceed. If it exists and points anywhere else, **halt and report the conflict** with the version string, the existing tag SHA, and HEAD. Never delete or move a tag without explicit authorization. Write the message to a file through a quoted-delimiter heredoc and pass it with `-F`, never inline with `-m`: the body carries backticks, which a double-quoted string runs as command substitution and silently deletes, and apostrophes, which end a single-quoted string. The tag message renders as the GitHub Release body via `--notes-from-tag`. It must be structured markdown, not a flat string. **Release PR mode: the tag body is the PR body's `## Changes` bullets plus its final changelog link, verbatim** — `gh pr view <N> --json body -q .body` (`<N>` from step 1 — on `main` there is no branch for `gh` to infer it from), take the bullets under `## Changes` and the last line; drop the PR's opening theme line, `## Gates`, and the headers. That digest was authored at wrapup and reviewed on the PR; re-authoring it here would publish unreviewed words. The subject is the one part not lifted — write it fresh, per the rules below. The one addition to the digest: append ` · release PR #<N>` to that final line, so the GitHub Release points at its audit trail (GitHub autolinks the bare `#<N>`). Without a PR, author the bullets from the changelog entry at `changelog/<major.minor>.x/<version>.md` — every claim in the tag must appear in that file. `--cleanup=whitespace` is load-bearing. The default cleanup (`strip`) deletes `#`-leading lines as comments, so markdown headers silently vanish from the tag body. `--cleanup=verbatim` is worse: it skips end-of-message normalization, so with tag signing enabled the signature is appended flush against the message's last character — git then can't parse its own signature (the tag reads as unsigned) and the whole `-----BEGIN SSH SIGNATURE-----` block publishes verbatim into the GitHub Release body. Format — a **headline digest**, never a section-by-section changelog mirror: ``` <subject — one short theme written for this tag, ~60 chars; omit the version number, GitHub prepends v<VERSION>:> - <notable user-facing change> (#N) - <notable user-facing change> (#N) - <ONE compact grouped line for the minor/internal changes — build config, repo hygiene, metadata> - deps: `@cyanheads/mcp-ts-core` ^0.10.6 → ^0.10.14 (+ dev-dep bumps) [CHANGELOG v<version>](https://github.com/<OWNER>/<REPO>/blob/main/changelog/<major.minor>.x/<version>.md) · release PR #<N> ``` (` · release PR #<N>` only in release PR mode; without a PR the line ends at the changelog link.) **Rules:** - **Subject line is ONE short theme, at most ~60 characters, no semicolons, no clauses** — it becomes the GitHub Release title after `v<VERSION>: `. The digest lives in the bullets; a subject that summarizes each change is wrong even when every word is accurate. **It is written for this tag, never lifted** — not from the changelog entry's `summary:`, which has a 350-character budget for a different surface, and not from the PR body's opening paragraph, which is that same line. The release commit's subject after the version and dash is usually the theme already - Subject line omits the version number (GitHub prepends `v<VERSION>:` to the release title) - **Flat bullets only — never Keep-a-Changelog section headers.** `Added:`/`Changed:`/`Fixed:`/`Dependency bumps:` belong in the changelog file; a tag that mirrors the changelog's structure is wrong even when every line is accurate - **Complete at headline granularity** — every changelog-worthy change stays visible: notable changes get their own bullet, minor/internal items (build config, repo hygiene, metadata) share ONE grouped compact bullet. Nothing silently dropped, nothing expanded — the changelog carries the depth, the tag carries the existence - **Deps: one line max**, naming only what earns it (the framework bump, a major); per-package arrows for the rest live in the changelog entry only - **No gates line** — test counts and devcheck status are changelog detail (and PR-body material in release PR mode), not release-body material - No narrative preamble — bullets under the subject, no paragraph blocks - No marketing adjectives - Length is earned — a subject + two bullets + changelog link is a fine tag for a small patch - **Issue backlinks:** when changes address GitHub issues, include `(#N)` references in the relevant bullets — same as the changelog entry. The backlinks render as clickable links in the GitHub Release body. - **Changelog link (final line):** end the tag body with a Markdown link to this version's changelog file, so the GitHub Release offers a one-click jump to the full entry — `[CHANGELOG v<version>](https://github.com/<OWNER>/<REPO>/blob/main/changelog/<major.minor>.x/<version>.md)`. Derive `<OWNER>/<REPO>` from the origin remote; the path mirrors the changelog file (e.g. `changelog/0.10.x/0.10.12.md`). Keep the blank line above it so it renders as its own paragraph. In release PR mode the same line continues with ` · release PR #<N>` — the release then links both the depth (changelog) and the audit trail (PR). Verify before moving on: ```bash git show v<version> --stat | head -20 # tag points at HEAD (the release commit, or the last review commit above it) git tag -l v<version> --format='%(if)%(contents:signature)%(then)signed%(else)unsigned%(end)' # with tag signing enabled, must print "signed" ``` `unsigned` under enabled tag signing means the signature didn't parse (see the cleanup note above) — delete and recreate the tag now, before it leaks the signature block into the GitHub Release body. This is the one tag deletion that needs no authorization: the tag is local, seconds old, and yours. ### 5. Push to origin ```bash git push origin main git push origin v<version> ``` Push `main` first, then the tag. If the remote rejects either push, halt. **Release PR mode, after both pushes:** confirm `gh pr view <N> --json state` reports `MERGED`, then delete the remote branch — `git push origin --delete release/<version>` — and the local one — `git branch -d release/<version>`. A PR that reports `CLOSED` or `OPEN` instead means the pushed `main` does not contain the PR's head commit — stop and report before publishing anything. ### 6. Publish to npm Before publishing, inspect `bun publish --dry-run`. A resumed run may leave `dist/*.mcpb` in a package whose `files` allowlist includes `dist/`, adding the desktop bundle and its dependencies to npm. If listed, move the bundle outside the package directory, publish npm, then restore the bundle for the GitHub Release. ```bash bun publish --access public ``` `bun publish` uses whatever npm auth the user has configured in `~/.npmrc`. If 2FA is enabled on the npm account, the command will prompt for an OTP or open a browser — that's expected; the user completes it interactively. **Friction reducers (optional, configure once):** | Option | How | |:--|:--| | **npm granular access token** with "Bypass 2FA for publish" | Generate at npmjs.com → replace `_authToken` in `~/.npmrc` → no OTP prompt at all | | **1Password CLI TOTP injection** (requires `brew install --cask 1password-cli` + signed-in `op`) | `bun publish --access public --otp="$(op item get 'npm' --otp)"` | Halt on publish error other than "version already exists" (which means this step already ran). ### 7. Publish to MCP Registry Only if `server.json` exists at the repo root (otherwise skip). Note: `server.json` (MCP Registry metadata) and `manifest.json` (MCPB bundle manifest, step 8) are independent — a project may have either, both, or neither. The registry checks that the npm version exists before it registers, and npm's read endpoint can lag `bun publish` by several minutes. Wait for the version to be served before publishing; a publisher error saying the npm version was not found is this lag, not a terminal failure: ```bash curl -sf --retry 30 --retry-delay 30 --retry-all-errors -o /dev/null \ "https://registry.npmjs.org/<package-name>/<version>" bun run publish-mcp ``` Step 8 depends only on the pushed tag, so it may run while this wait is in progress. If `publish-mcp` isn't defined in `package.json`, add it permanently (one-time setup, macOS): ```json "publish-mcp": "mcp-publisher login github -token \"$(security find-generic-password -a \"$USER\" -s mcp-publisher-github-pat -w)\" && mcp-publisher publish" ``` Prereq: a GitHub PAT with `read:org` + `read:user` scopes stored in Keychain under the service name `mcp-publisher-github-pat`: ```bash security add-generic-password -a "$USER" -s mcp-publisher-github-pat -w # paste PAT at the silent prompt ``` Halt on any publisher error other than "cannot publish duplicate version". ### 8. Create GitHub Release Pre-flight: `--notes-from-tag` publishes the tag message as-is. With tag signing enabled, confirm the tag's signature parses — `git tag -l v<version> --format='%(contents:signature)'` must be non-empty. Empty on a signing-enabled repo (e.g. a tag created with `--cleanup=verbatim`) means git is treating the signature as message text, and the `-----BEGIN SSH SIGNATURE-----` block will land in the public release body — the tag is already pushed by now, so halt and report rather than recreating it silently. For all projects (including those without `manifest.json`): ```bash bun run release:github ``` The script (`scripts/release-github.ts`) handles everything in one command: - Reads `version` from `package.json` - Derives the tag subject via `git for-each-ref refs/tags/v<version>` - Runs `gh release create v<version> --verify-tag --notes-from-tag --title "v<version>: <subject>"` - Attaches `dist/*.mcpb` when `manifest.json` exists (skip the `bun run bundle` step first if not already built — see below) - On "release already exists" (re-invocation after a prior partial run): uploads/clobbers the `.mcpb` asset (if applicable) and patches the title via `gh release edit` **If `manifest.json` exists**, build the bundle first so the asset is ready: ```bash bun run bundle # produces dist/<name>.mcpb (stable filename, no version) bun run release:github # attach + release in one step ``` The stable filename matters: it lets the README "Install in Claude Desktop" badge point at `releases/latest/download/<name>.mcpb` and always resolve to the most recent release. The `bundle` script in the templates outputs `dist/{{PACKAGE_NAME}}.mcpb` for this reason. Deterministic download URLs (for MCPB projects): - Pinned to this version: `https://github.com/<OWNER>/<REPO>/releases/download/v<VERSION>/<name>.mcpb` - Always latest (powers the install badge): `https://github.com/<OWNER>/<REPO>/releases/latest/download/<name>.mcpb` If `server.json` includes an MCPB `packages[]` entry, its `identifier` should match this URL and `fileSha256` should match `shasum -a 256 <bundle>` — keep these in sync during wrapup, not here. **Framework note:** `mcp-ts-core` has no `manifest.json` — the bundle attach path is skipped automatically. Skip the Docker build/push step too (this framework package is consumed via npm, not as a container image). Halt on any non-zero exit not handled by the script's built-in fallback. ### 9. Publish Docker image Only if `Dockerfile` exists at the repo root (otherwise skip). Derive: - `OWNER/REPO` from the origin remote URL — use your git tools to read it; strip `.git`, handle both `https://github.com/<owner>/<repo>` and `git@github.com:<owner>/<repo>` forms - `VERSION` from `package.json` (step 1) ```bash docker buildx build --platform linux/amd64,linux/arm64 \ --build-arg APP_VERSION=<VERSION> \
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기