Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 48 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,34 @@ on:
name: Release workflow

jobs:
# A prerelease tag (any version carrying a SemVer prerelease identifier, e.g.
# v21.3.0-beta.0) must NOT be treated like a stable release: npm does not
# special-case prerelease versions, so publishing one without an explicit dist-tag
# would point `latest` at it and hand it to every `npx eas-cli` user. Every job below
# branches on this one output instead of re-deriving it from the tag.
resolve:
name: Resolve release kind
runs-on: ubuntu-latest
outputs:
version: ${{ steps.resolve.outputs.version }}
prerelease: ${{ steps.resolve.outputs.prerelease }}
steps:
- id: resolve
run: |
VERSION="${GITHUB_REF#refs/tags/v}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# SemVer puts the prerelease identifier after a hyphen: 21.3.0-beta.0.
if [[ "$VERSION" == *-* ]]; then
echo "prerelease=true" >> "$GITHUB_OUTPUT"
echo "::notice::$VERSION is a prerelease: publishing under the 'next' dist-tag, leaving 'latest', the EAS Build tags and CHANGELOG.md untouched."
else
echo "prerelease=false" >> "$GITHUB_OUTPUT"
fi

release:
name: Create a release
runs-on: ubuntu-latest
needs: resolve
permissions:
contents: write
outputs:
Expand All @@ -21,11 +46,15 @@ jobs:
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: true
# A stable release stays a draft until the changelog job publishes it with
# notes. A prerelease skips that job, so publish it immediately and flag it so
# GitHub never presents it as the repo's latest release.
draft: ${{ needs.resolve.outputs.prerelease == 'false' }}
prerelease: ${{ needs.resolve.outputs.prerelease == 'true' }}
publish-to-npm:
name: Publish to npm
runs-on: ubuntu-latest
needs: release
needs: [resolve, release]
permissions:
id-token: write
steps:
Expand All @@ -35,26 +64,41 @@ jobs:
run: yarn install --immutable
- name: Build
run: yarn build
# Always pass the dist-tag explicitly. `latest` is what npm would have defaulted
# to, so stable releases are unchanged — but relying on that default is what made
# a prerelease publish unsafe, so the intent is stated rather than inherited.
- name: Publish packages to npm
run: yarn run -T lerna publish from-package --yes --no-verify-access
env:
DIST_TAG: ${{ needs.resolve.outputs.prerelease == 'true' && 'next' || 'latest' }}
run: yarn run -T lerna publish from-package --yes --no-verify-access --dist-tag "$DIST_TAG"
# These two tags select the CLI version used by EAS Build. A prerelease must never
# move them (that would put untested code in production builds); `latest-eas-build`
# also has its own gated workflow for moving it deliberately.
- name: Add latest-eas-build-staging tag
if: needs.resolve.outputs.prerelease == 'false'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
run: |
VERSION=$(cat lerna.json | jq -r .version)
echo //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN > ~/.npmrc
npm dist-tag add eas-cli@$VERSION latest-eas-build-staging
- name: Add latest-eas-build tag
if: needs.resolve.outputs.prerelease == 'false'
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
run: |
VERSION=$(cat lerna.json | jq -r .version)
echo //registry.npmjs.org/:_authToken=$NODE_AUTH_TOKEN > ~/.npmrc
npm dist-tag add eas-cli@$VERSION latest-eas-build
# Stable releases only. A prerelease must leave CHANGELOG.md alone: its entries stay
# under "unreleased" so the eventual stable release still announces them. Skipping this
# job also keeps a prerelease from committing to main and from Slack-announcing itself
# as a release — the GitHub prerelease is its announcement.
release-changelog:
name: Update changelog and publish release
if: needs.resolve.outputs.prerelease == 'false'
runs-on: ubuntu-latest
needs: publish-to-npm
needs: [resolve, publish-to-npm]
permissions:
contents: write
steps:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/trigger-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
version:
type: string
required: false
description: 'Version number for the release'
description: 'Version for the release, e.g. "21.3.0". Prerelease: "21.3.0-beta.0" (publishes under the `next` dist-tag; see RELEASING.md). Leave empty to derive the next stable version from the changelog.'
dry_run:
type: boolean
required: true
Expand Down
75 changes: 75 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,78 @@ The algorithm works as follows:
- If there are any entries in the "🛠 Breaking changes" section, bump the MAJOR version.
- Otherwise, if there are any entries in the "🎉 New features", bump the MINOR version.
- Otherwise, bump the PATCH version.

## Prereleases

A prerelease publishes a real version to npm that nobody gets by accident: it goes out
under the `next` dist-tag, so `latest` keeps pointing at the current stable release and
`npx eas-cli` is unaffected. Use one to get a risky change in front of users (or to dogfood
it in a real project) before committing to a stable release.

**Cutting one:** invoke the same ["Manually trigger a release"](https://github.com/expo/eas-cli/actions/workflows/trigger-release.yml)
workflow, but pass an explicit prerelease version — e.g. `21.3.0-beta.0`. Any version with a
SemVer prerelease identifier (the part after the hyphen) is treated as a prerelease; the
identifier itself is free-form, so `-beta.0`, `-rc.1` and `-canary.0` all work. You must pass
the version explicitly: the changelog-based algorithm above only ever produces stable versions.

**Installing one:**

```sh
npm i -g eas-cli@next # whatever the current prerelease is
npm i -g eas-cli@21.3.0-beta.0 # a specific one
```

**What a prerelease does differently.** Everything is driven off the pushed tag by
`.github/workflows/release.yml`, which detects the prerelease identifier and then:

| | Stable | Prerelease |
| ------------------------------------ | ----------------- | -------------------------------- |
| npm dist-tag | `latest` | `next` |
| `latest-eas-build` / `-staging` tags | moved | **left alone** |
| CHANGELOG.md | cut and committed | **left alone** |
| GitHub release | draft → published | published, flagged as prerelease |
| Slack announcement in #eas-cli | yes | no |

The EAS Build tags are deliberately untouched: they select the CLI version used by EAS
Build, so moving them to a prerelease would put untested code into production builds.
Move them on purpose with the ["Move NPM tags used by EAS builds and workflows"](https://github.com/expo/eas-cli/actions/workflows/move-eas-build-tag.yml)
workflow if that is what you actually want.

CHANGELOG.md is also untouched, so a prerelease's entries stay under "unreleased" and are
announced with the stable release that follows.

### Prereleases in this monorepo

Prereleases version and publish packages exactly like stable releases do — lerna runs in
fixed mode, so it applies the single version from `lerna.json` to the packages that changed
since the last release and leaves the rest alone (which is why the packages sit at different
versions). `lerna publish from-package` then publishes whichever of those aren't on the
registry yet, and the `next` dist-tag applies to every package published in that run.
`latest` is untouched for all of them, so nothing that depends on `@expo/eas-json`,
`@expo/steps`, `@expo/eas-build-job` or friends picks up a prerelease implicitly.

Two consequences worth knowing:

- **The version line is shared.** After `21.3.0-beta.0`, the next stable release from main
is `21.3.0` — the prerelease consumes that version number for the whole repo. Cutting a
`21.2.1` patch afterwards means passing that version explicitly.
- **main carries the prerelease version.** `lerna version` commits the bump, so `lerna.json`
and the changed packages sit at `21.3.0-beta.0` until the next release. The changelog-based
algorithm still resolves correctly from there (a MINOR or PATCH bump off `21.3.0-beta.0`
both land on `21.3.0`).

### Testing an unmerged branch

Prereleases are cut from `main`, so they are not the tool for validating a PR before it
merges. For that, build the branch locally and run it directly:

```sh
yarn && yarn build
./packages/eas-cli/bin/run --version
```

or pack a tarball to install elsewhere:

```sh
cd packages/eas-cli && yarn pack # then: npm i -g ./eas-cli-*.tgz
```
Loading