Releasing
This page documents how the techdocs site is generated and published, and the steps required to cut a new tagged release — including alpha/beta prereleases — to npm. It’s aimed at maintainers, not contributors submitting merge requests.
How techdocs are published
The documentation site is a static site built with
Eleventy. Running npm run build compiles the docs (along with the component library itself) and
outputs the static site to _site/.
GitLab CI’s pages job takes that output and publishes it as a GitLab Pages site — but only when
building from a specific branch:
pages: stage: pages script: - mv _site public artifacts: paths: - public/ rules: - if: '$CI_COMMIT_BRANCH == "release/1.2"'
This means
the live docs site always reflects the current stable release, not main. main is where prerelease work for the next minor version happens, so if the
pages job ran off main, users would see documentation for components and APIs that
aren’t actually published to npm yet.
Every time a new minor version stabilizes, this rule is updated to point at the new
release/x.y branch (see
Cutting a stable release below).
Branching strategy
-
main— active development for the next minor release. Version inpackage.jsoncarries a-alpha(or-beta) prerelease identifier, e.g.1.2.0-alpha.1. -
release/x.y— a maintenance branch cut once a minor version stabilizes, e.g.release/1.1. This is the source of truth for that minor’s docs site and receives patch fixes (1.1.1,1.1.2, …) independently ofmain.
Each minor line gets its own release/x.y branch. Older release branches (e.g.
release/1.0) stay around for reference and historical patch releases but are no longer wired to
the pages job once a newer one takes over.
Tagging convention
Releases are tagged vX.Y.Z (matching
SemVer),
including prerelease identifiers, e.g. v1.2.0-alpha.1.
The CI publish job runs on any tag matching ^v and enforces that the tag matches
package.json’s version field exactly:
publish: stage: publish rules: - if: '$CI_COMMIT_TAG =~ /^v/' id_tokens: NPM_ID_TOKEN: aud: 'npm:registry.npmjs.org' before_script: - npm ci --ignore-scripts script: - | VERSION="$(node -p "require('./package.json').version")" PKG_VERSION="v$VERSION" if [ "$CI_COMMIT_TAG" != "$PKG_VERSION" ]; then echo "Tag $CI_COMMIT_TAG does not match package.json version ($PKG_VERSION)" exit 1 fi NPM_TAG="latest" if [[ "$VERSION" == *-* ]]; then NPM_TAG=$(echo "$VERSION" | sed -E 's/^[^-]+-([a-zA-Z]+).*/\1/') fi - npm publish --access public --ignore-scripts --tag "$NPM_TAG"
The npm dist-tag is derived automatically from the version string:
-
A plain version like
1.2.0publishes to thelatestdist-tag — whatnpm install @cordwainer/cw-elementsresolves to by default. -
A prerelease version like
1.2.0-alpha.1or1.3.0-beta.0publishes under its prerelease identifier’s dist-tag (alphaorbeta), so it never overwriteslatest. Users opt in explicitly withnpm install @cordwainer/cw-elements@alpha.
Publishing is CI-only. It authenticates to npm via
OIDC trusted publishing
(NPM_ID_TOKEN), so there’s no npm token to manage and
npm publish should never be run locally. Pushing the correct tag is what
triggers a release.
Cutting a prerelease (alpha/beta)
Prereleases are cut directly from main whenever there’s alpha/beta-worthy work to ship for
testing ahead of a stable minor.
-
Bump the version in
package.json, incrementing the prerelease number:npm version 1.2.0-alpha.2 --no-git-tag-version -
Commit the version bump:
git add package.json package-lock.json git commit -m "1.2.0-alpha.2" -
Tag and push:
git tag v1.2.0-alpha.2 git push origin main --tagsThe
publishjob picks up the tag and publishes to thealphadist-tag.
Cutting a stable release
Once a minor version is ready to stabilize:
-
On
main, bumppackage.jsonto the stable version, finalize the changelog, repoint thepagesjob at the new maintenance branch, and commit all three together:npm version 1.2.0 --no-git-tag-versionIn
docs/pages/resources/changelog.md, rename the version’s existing## 1.2.0-alpha.Nheading to## 1.2.0(don’t stack a separate heading — the alpha entries collapse into the one stable entry, since users on thelatestdist-tag never saw the alpha churn). Add any changes made since the last alpha as additional bullets under that same heading.In
.gitlab-ci.yml, update thepagesjob’s rule fromrelease/1.1torelease/1.2.git add package.json package-lock.json docs/pages/resources/changelog.md .gitlab-ci.yml git commit -m "chore: release 1.2.0 and cut release/1.2 maintenance branch"All three changes land in this one commit, directly on
main, beforerelease/1.2exists — not as a follow-up merged into both branches afterward.release/1.2is about to be branched from this exact commit, so it automatically inherits the updated changelog and the repointedpagesrule the moment it’s created. If you cut the branch first and update these afterward, you’d have to make the same edit twice andmain/release/1.2would disagree about what shipped in1.2.0in the meantime. -
Cut the maintenance branch from that commit and push it:
git checkout -b release/1.2 git push origin release/1.2 -
Tag the release on
release/1.2and push the tag:git tag v1.2.0 git push origin v1.2.0The
publishjob publishes to thelatestdist-tag, and thepagesjob (now matchingrelease/1.2) publishes the live docs site from this branch going forward. -
Back on
main, start the next prerelease line:npm version 1.3.0-alpha.0 --no-git-tag-version git add package.json package-lock.json git commit -m "chore: begin v1.3.0 alpha development on main" git push origin main
Patch fixes for a stable line (e.g. 1.2.1) are made directly on the
release/x.y branch and tagged the same way, without touching main.