diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2442619 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,58 @@ +name: Release + +on: + push: + branches: [main] + +concurrency: ${{ github.workflow }}-${{ github.ref }} + +jobs: + release: + name: Version & Publish + runs-on: ubuntu-latest + permissions: + contents: write # push the version-bump commit + id-token: write # OIDC trusted publishing to npm (no token secret) + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + registry-url: https://registry.npmjs.org + + # Trusted publishing over OIDC needs npm >= 11.5.1; Node 20 ships an older npm. + - run: npm install -g npm@latest + + - run: yarn install --frozen-lockfile + + - name: Check for changesets + id: changesets + run: | + COUNT=$(find .changeset -name '*.md' ! -name 'README.md' | wc -l | tr -d ' ') + echo "count=$COUNT" >> "$GITHUB_OUTPUT" + if [ "$COUNT" != "0" ]; then + echo "Found $COUNT changeset(s) — will version and publish." + else + echo "No changesets found — skipping." + fi + + # Consume changesets: bump version, write CHANGELOG, remove changeset files. + - name: Version packages + if: steps.changesets.outputs.count != '0' + run: | + yarn changeset version + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "chore: version packages [skip ci]" + git push + + # Publish with the bumped version. No NODE_AUTH_TOKEN: npm exchanges the + # OIDC id-token for a short-lived credential. Requires a trusted publisher + # configured for this package on npmjs.com (repo + workflow file name). + - name: Build & Publish + if: steps.changesets.outputs.count != '0' + run: | + yarn build + yarn changeset publish diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 221a8c8..0dbdc4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,28 +3,50 @@ ### Setup ```bash -npm install -npm run build # Builds both library and CLI +yarn install +yarn build # Builds both library and CLI ``` +Use **yarn**, not npm. The committed lockfile is `yarn.lock` and CI runs +`yarn install --frozen-lockfile`; installing with npm drifts the lockfile and +breaks the build. + ### Build commands ```bash -npm run build # Full build (library + CLI) -npm run build:lib # Library only -npm run build:cli # CLI only +yarn build # Full build (library + CLI) +yarn build:lib # Library only +yarn build:cli # CLI only ``` ### Publishing a new version -Publishing is **manual** via `npm`. From `main` with a clean working tree: +Releases are automated. On merge to `main`, the Release workflow +(`.github/workflows/release.yml`) consumes any pending changesets, bumps the +version, updates the changelog, and publishes to npm over OIDC. No npm token is +involved — the repository publishes itself via a trusted publisher. + +To cut a release: + +1. In your PR, add a changeset and pick the bump level (patch / minor / major): + + ```bash + yarn changeset + ``` + + This writes a file under `.changeset/`. Choosing the bump level is the manual + part of the release; anyone can do it in a PR without a token. + +2. Merge to `main`. The workflow versions and publishes the new release. + +#### Manual publish (break-glass only) + +Only if CI is unavailable. This needs an npm token and a clean changeset state +(no pending `.changeset/*.md`, or the next merge double-bumps). From `main` with +a clean tree: ```bash -npm version patch --no-git-tag-version # bump package.json (default to a patch) -npm publish # prepublishOnly runs the full build first -git commit -am "chore(release): @thatopen/services " -git push +yarn changeset version # bump package.json + changelog +yarn build && yarn changeset publish # prepublishOnly runs the full build +git commit -am "chore(release): @thatopen/services " && git push ``` - -Keep semver in mind, but default to a **patch** unless a maintainer explicitly -calls for a minor or major.