Skip to main content
Light Dark System

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 in package.json carries 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 of main.

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.0 publishes to the latest dist-tag — what npm install @cordwainer/cw-elements resolves to by default.
  • A prerelease version like 1.2.0-alpha.1 or 1.3.0-beta.0 publishes under its prerelease identifier’s dist-tag (alpha or beta), so it never overwrites latest. Users opt in explicitly with npm install @cordwainer/cw-elements@alpha.

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.

  1. Bump the version in package.json, incrementing the prerelease number:

    npm version 1.2.0-alpha.2 --no-git-tag-version
    
  2. Commit the version bump:

    git add package.json package-lock.json
    git commit -m "1.2.0-alpha.2"
    
  3. Tag and push:

    git tag v1.2.0-alpha.2
    git push origin main --tags
    

    The publish job picks up the tag and publishes to the alpha dist-tag.

Cutting a stable release

Once a minor version is ready to stabilize:

  1. On main, bump package.json to the stable version, finalize the changelog, repoint the pages job at the new maintenance branch, and commit all three together:

    npm version 1.2.0 --no-git-tag-version
    

    In docs/pages/resources/changelog.md, rename the version’s existing ## 1.2.0-alpha.N heading to ## 1.2.0 (don’t stack a separate heading — the alpha entries collapse into the one stable entry, since users on the latest dist-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 the pages job’s rule from release/1.1 to release/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"
    
  2. Cut the maintenance branch from that commit and push it:

    git checkout -b release/1.2
    git push origin release/1.2
    
  3. Tag the release on release/1.2 and push the tag:

    git tag v1.2.0
    git push origin v1.2.0
    

    The publish job publishes to the latest dist-tag, and the pages job (now matching release/1.2) publishes the live docs site from this branch going forward.

  4. 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.