# Release

> Cut and publish a Gerillass release — version bump, changelog, tag, GitHub release, npm publish, and post-publish verification. Use when the user asks to release, publish, ship a version, or push a fix out to npm.

- Skill: `selfishprimate/release` (Agent Skill)
- Install (CLI): `npx skillmds@latest add selfishprimate/release`
- Raw SKILL.md: https://api.skillmd.com/api/skills/selfishprimate/release/raw
- Safety review: pending (external: skill-scanner PASS, skillspector CAUTION)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: selfishprimate (https://skillmd.com/u/selfishprimate)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/selfishprimate/release

---


# Release Gerillass

The repository and the npm package are separate things. Fixing `main` changes
nothing for anyone using the library — the fix reaches users only when a new
version is published. Never stop at the git tag.

Take the target version from the user. If they did not name one, propose one and
confirm before touching anything:

- **patch** for dependency, security and bugfix work. This is what almost every
  release here has been, including dependency restructuring (see 1.2.6, 1.3.3).
- **minor** for a new mixin or function.
- **major** for a change that breaks a call that worked, such as a removed
  member or a changed argument order. 2.0.0 was the module migration; 3.0.0
  replaced the gradient mixins.

Every release publishes to **both** registries, npm (step 6) and RubyGems
(step 6b). Every **major** release also rewrites the site's announcement band
(step 3c). Both are rules the maintainer set; do not skip either.

## 1. Verify the tree before anything else

```bash
npm test                    # must pass: specs, smoke test and the manifest suite
yarn audit                  # must be zero across all severities
node tools/audit.js         # the SILENT bucket must be empty
node tools/check-docs.js    # the docs must not claim a count the repo lacks
node tools/check-wiki.js    # names what the wiki page has to cover
git status --short          # know exactly what is going out
```

The library must no longer trip the two deprecations the module migration was
about. This must print nothing:

```bash
npx sass --load-path=scss test/smoke.scss 2>&1 >/dev/null | grep -E 'DEPRECATION.*(import|global-builtin)'
```

A plain `grep DEPRECATION` will still find something, and that is expected: Sass
is deprecating its own `if()`, which the library calls in 21 places. Use `npx`,
not a system `sass` — an older one has nothing to say about `if()` and will make
a stale tree look clean.

If `dependencies` in `package.json` is non-empty, stop and fix that first — it is
the single most consequential defect this package can ship. See CLAUDE.md.

## 2. Bump and document

Edit `version` in `package.json`, then add an entry at the top of
`CHANGELOG.md`, directly above the previous version heading. Match the existing
bullet style exactly:

```markdown
## 1.3.4

- **Security:** ...
- **Added:** ...
- **Updated:** ...
- **Removed:** ...
- **Fixed:** ...
```

Write what changed for a **user of the library**, not what changed in the repo.

## 3. Confirm the tarball

```bash
npm pack --dry-run
```

`llms.txt` is generated too, and its links are the one thing no test can check,
because they point at sites this repository does not build:

```bash
node tools/check-links.js
```

Run it on **every** release, not only ones that add a member: a page published
on the documentation site since the last release should stop linking to source.
v2.1.0 shipped with `line-clamp` pointing at a page that did not exist, because
this step was a loop to copy and it was skipped.

Expect 114 files, an 82 kB tarball (3.0.0): everything under `scss/`, the generated
`gerillass.json` and `SKILL.md`, plus `README.md`, `LICENSE.md` and
`package.json`. If `test/`, `meta/`, `tools/`, `yarn.lock` or
`node_modules` appear, `.npmignore` is broken — stop and fix it.

The source zip and tarball GitHub attaches to the release are a different
package. They are built with `git archive` from the tag, and `.npmignore` does
not reach them; `export-ignore` in `.gitattributes` does. They are meant to hold
the library's buildable source, not only what npm ships, so check them against
their own list:

```bash
node tools/check-archive.js
```

It must pass before tagging. An unexpected entry means something was committed
that `.gitattributes` does not keep out, which is how `site/` was headed for the
first release after 2.1.0 without anyone deciding it.

## 3b. Write the wiki page for the other repositories

A release is not finished when npm has it. `gerillass.com` and
`docs.gerillass.com` are separate repositories whose sessions cannot see this
one, and a changelog does not tell them which control on which page to touch.

Add `wiki/vX.Y.Z.md`, following `wiki/README.md` and an existing file.
`node tools/check-wiki.js` lists exactly which members it has to name, worked
out from the manifest at the previous tag, and fails until the page covers all
of them. A hook runs it too, so bumping the version in step 2 before writing
the page will block. Do not
restate the API: `gerillass.json` already holds it and is verified. Write what
is not in there.

If the release adds or changes a member, the page must cover:

- **the playground member `<select>` on `gerillass.com`**, which needs every
  new mixin adding by hand, and the version `<select>`, which needs the new
  version;
- **which documentation pages to write or update** on `docs.gerillass.com`;
- **the measurement behind each member**. This is the part with no other home,
  and it is what a documentation page should be built on.

## 3c. The site, which advertises the release

`gerillass.com` and its documentation live under `site/`, and several places on
it name the version. A release that updates the library and not these leaves
the site advertising the old one, or linking to a download that does not exist.

| Where | How it follows the version | What to do |
|---|---|---|
| Header badge under the logo | `site/src/release.js` reads `VERSION` from the root `package.json` | nothing to edit; confirm it after a build |
| Both download buttons | `DOWNLOAD_URL` in the same file, `archive/refs/tags/vX.Y.Z.zip` | the link is a 404 until the tag is pushed, so **deploy the site only after step 4** |
| Playground version menu | reads the npm registry; `FALLBACK` in `site/src/components/Playground/versions.js` is the seed used when that fails | add the version to `versions` and `latest` **after npm has it** (step 7), not before |
| Stargazers row | a committed snapshot | `npm run supporters --prefix site` |
| Playground starter snippets | `demos.json`, generated from `site/content/docs/*.mdx` and the root `gerillass.json` | `npm run playground-demos --prefix site` when a documented example or a page description changed, and whenever a mixin is added |
| Documentation pages | `site/content/docs/*.mdx` | compile every example against the previous tag and `HEAD`; for each member whose behaviour changed, read its argument table and footnotes for claims that are no longer true |
| Announcement banner | hand-written in `site/src/components/Announcement/index.jsx` | **every major release**: name the version, say in one sentence what breaks, link `MIGRATION.md`. A rule since 17 September 2026. Leave it for a minor or patch |

Confirm the badge and the download link in the build before tagging:

```bash
npm run build --prefix site
grep -o "X.Y.Z" site/dist/index.html | sort | uniq -c
grep -o 'archive/refs/tags/v[^"]*' site/dist/index.html
```

After the tag is pushed, check that the link resolves:

```bash
git ls-remote --tags https://github.com/selfishprimate/gerillass.git | grep vX.Y.Z
```

## 4. Commit, tag, push

```bash
git add package.json CHANGELOG.md
git commit                       # subject: "Release vX.Y.Z"
git tag -a vX.Y.Z -m "vX.Y.Z"    # tag format is vX.Y.Z — no dot after the v
git push origin main
git push origin vX.Y.Z
```

The default branch is `main`, protected against force-push and deletion but open
to direct pushes.

## 5. GitHub release

```bash
gh release create vX.Y.Z --repo selfishprimate/gerillass \
  --title "vX.Y.Z" --latest --notes-file <file>
```

Reuse the changelog entry as the notes, and end with a compare link:
`https://github.com/selfishprimate/gerillass/compare/vPREV...vX.Y.Z`

## 6. Publish to npm

```bash
npm whoami       # must print selfishprimate
npm publish
```

The account has **2FA enabled**, so `npm publish` fails with `EOTP`. The
one-time code is a credential — do not ask for it and do not type it. Tell the
maintainer to run this themselves:

```bash
npm publish --otp=<code>
```

## 6b. Publish to RubyGems

The gem is the same release: `gerillass.gemspec` reads its version from
`package.json`, so there is nothing to bump. It ships `scss/`, `gerillass.json`,
`SKILL.md`, `lib/`, `LICENSE.md` and `README.md` and has no runtime
dependencies. The two agent files are committed build output and the gem takes
them as they are.

**Build it from the tag, never from a branch.** 2.3.1 was built from a branch
that already held a commit made after `v2.3.1`, and the gem shipped three
warning messages that differ from the npm package of the same version. A
version cannot be pushed twice, so a difference like that stays until the next
release. Build in a worktree of the tag, outside the repository so no `.gem` is
left at the root, and check what is in it before pushing:

```bash
out="$(mktemp -d)"
git worktree add "$out/src" vX.Y.Z
(cd "$out/src" && gem build gerillass.gemspec --output "$out/gerillass-X.Y.Z.gem")
git worktree remove "$out/src"
ruby -e 'require "rubygems/package"; s = Gem::Package.new(ARGV[0]).spec
  puts s.version, s.runtime_dependencies.inspect, s.files.size,
       s.files.reject { |f| f.start_with?("scss/") }' "$out/gerillass-X.Y.Z.gem"
```

Expect `X.Y.Z`, `[]`, one file per `scss/` partial plus seven, and those seven
being `gerillass.json`, `SKILL.md`, `LICENSE.md`, `README.md` and the three
files under `lib/`.

Then compare it with what npm published for the same version. Every command
must print nothing:

```bash
gem unpack "$out/gerillass-X.Y.Z.gem" --target "$out/gem"
mkdir "$out/npm" && (cd "$out/npm" && tar -xzf "$(npm pack gerillass@X.Y.Z --silent)")
diff -r "$out/gem/gerillass-X.Y.Z/scss" "$out/npm/package/scss"
diff "$out/gem/gerillass-X.Y.Z/gerillass.json" "$out/npm/package/gerillass.json"
diff "$out/gem/gerillass-X.Y.Z/SKILL.md" "$out/npm/package/SKILL.md"
```

The macOS system Ruby is too old for the integrations; use Homebrew's
(`/opt/homebrew/opt/ruby/bin`). The gemspec is kept to ASCII on purpose: RubyGems
reads it in the locale's encoding and a literal non-ASCII character fails to
load. `rubygems_mfa_required` is set, so the push needs a one-time code, and
signing in (`gem signin`) is the maintainer's to do. Tell them to run:

```bash
gem push "$out/gerillass-X.Y.Z.gem" --otp=<code>
```

## 7. Verify what actually shipped

npm reports "may take a few minutes to become available", so the registry lags
the publish. Poll rather than assuming:

```bash
until [ "$(npm view gerillass version)" = "X.Y.Z" ]; do sleep 10; done
npm view gerillass@X.Y.Z dependencies      # MUST be empty
npm view gerillass@X.Y.Z dist.shasum       # must match the shasum npm pack printed
```

Then confirm the consumer experience end to end:

```bash
cd "$(mktemp -d)" && npm init -y >/dev/null && npm install gerillass@X.Y.Z
ls node_modules | grep -v '^\.'            # expect gerillass and nothing else
npm audit
```

Then seed the playground's `FALLBACK` with the new version, as in step 3c, and
build the site.

Report the published version, the dependency count a consumer now installs, and
the audit result. If any Dependabot alerts or PRs were resolved by this release,
close them and say so.

