Skip to content
Merged
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
5 changes: 5 additions & 0 deletions docs/ci-cd-design.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# RelayFile CI/CD Pipeline Design

> **Design intent, not current state.** Some of this has drifted from what ships
> — publishing lives in `publish.yml` (not `publish-npm.yml`) and authenticates
> with OIDC trusted publishing rather than the npm token described below. For
> the steps to actually cut a release, see [`releasing.md`](./releasing.md).

## Overview

Four GitHub Actions workflows covering continuous integration, SDK publishing, binary releases, and Cloudflare Workers deployment. Designed to match the proven patterns from the relaycast project.
Expand Down
90 changes: 90 additions & 0 deletions docs/releasing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Releasing relayfile

Operational runbook for cutting a release. For the pipeline's design intent, see
[`ci-cd-design.md`](./ci-cd-design.md).

## Cutting a release

`publish.yml` (`workflow_dispatch`) is the only supported way to publish. One run
version-bumps every package, publishes them, commits `chore(release): vX.Y.Z`,
and creates the GitHub release.

```bash
gh workflow run publish.yml --ref main \
-f package=all -f version=patch -f tag=latest -f dry_run=false
```

| Input | Notes |
| --- | --- |
| `package` | Must be `all` for a real publish. The workflow hard-fails otherwise: it rewrites every manifest, and lockfile regeneration needs every `@relayfile/mount-*` package already published at the new version so npm can resolve tarball integrity metadata. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The wildcard @relayfile/mount-* does not match the directory name packages/local-mount (which typically maps to @relayfile/local-mount). If the package is named @relayfile/local-mount, update this reference to @relayfile/local-mount or @relayfile/*-mount to prevent confusion.

| `version` | `patch` / `minor` / `major`, or `prerelease` with `preid=rc` for a release candidate. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Clarify how preid=rc is passed to the workflow. If preid is a workflow input (e.g., -f preid=rc), it should be documented in the table and the example command. If it is automatically handled by the workflow when version=prerelease is selected, explicitly stating that would prevent confusion for operators.

| `tag` | `latest` for stable, `next` for prereleases. |

The bump is computed from the root `package.json` **on the ref you dispatch
from**, so check it before dispatching. Note `patch` on a prerelease drops the
prerelease rather than bumping the patch digit: `0.10.27-rc.0` → `0.10.27`.

## Promoting an RC, and the `next` tag

**Promoting an RC to stable strands the `next` tag.** `next` keeps pointing at
the RC you just superseded, so `@next` resolves *older* than `@latest`:

```
latest: 0.10.27
next: 0.10.27-rc.0 # older than latest — anyone on @next is behind
```

`npm dist-tag add relayfile@0.10.27 next` would fix this in one command, but it
**cannot run in this repo's CI**. `publish.yml` authenticates with npm OIDC
trusted publishing, and npm's OIDC covers only `npm publish` / `npm stage
publish` — every other command, `dist-tag` included, still needs a traditional
token ([npm/cli#8547](https://github.com/npm/cli/issues/8547)). This repo holds
no npm token secret by design, so there is nothing for `dist-tag` to
authenticate with.

The fix is a second dispatch that publishes a new version *forward* onto `next`
— `npm publish` is the one command OIDC does cover:

```bash
gh workflow run publish.yml --ref main \
-f package=all -f version=patch -f tag=next -f dry_run=false
```

```
latest: 0.10.27 # stable, unchanged
next: 0.10.28 # newer than latest
```

Run this after **every** RC → stable promotion, or `next` stays stranded until
the next RC.

The trade-off: this publishes a version whose content is identical to the stable
release, so version numbers advance faster than shipped changes. That is the
cost of not holding a long-lived npm token. The alternative is a granular npm
token scoped to `relayfile` + `@relayfile/*`, which would allow a single
`dist-tag` step instead — a deliberate security trade, not an oversight.

## After the release

Verify against the registry rather than the workflow log — a green run does not
prove the tags are right:

```bash
npm view relayfile dist-tags
npm view @relayfile/client dist-tags
```

The `next` dispatch creates its own GitHub release and GitHub marks the newest
tag "Latest", even though npm's `latest` is the stable version. Realign so the
releases page matches the registry:

```bash
gh release edit v0.10.27 --latest # the stable version, not the next-tag one

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Use a placeholder like v<stable-version> instead of the hardcoded v0.10.27 in the gh release edit command. Since this is a runbook, operators are likely to copy-paste commands directly, and using a placeholder prevents accidental edits to the wrong release version.

Suggested change
gh release edit v0.10.27 --latest # the stable version, not the next-tag one
gh release edit v<stable-version> --latest # the stable version, not the next-tag one

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Replace the hard-coded stable release tag

On every promotion after this example, copying this runbook command will mark the old v0.10.27 release as Latest instead of the newly promoted stable version. This affects more than the releases page: scripts/install.sh:48-53 and scripts/install.sh:122-130 resolve /releases/latest and download binaries from that tag, so fresh installations can be downgraded. Use an explicit placeholder or a computed stable-version variable here.

Useful? React with 👍 / 👎.

```

## Consumers

Downstream `^x.y.z` ranges resolve by semver across **all** published versions —
dist-tags do not gate them. A version published only to `next` will still
satisfy a caret range in a consumer's manifest on any lockfile refresh. Committed
Comment on lines +87 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Exclude prereleases from the caret-range claim

Bare caret ranges do not resolve across all published versions: for example, 0.10.28-rc.0 does not satisfy ^0.10.27 because semver excludes prereleases unless the range explicitly opts into them. Only the stable 0.10.28 version produced by the workaround will enter that range, so this statement should distinguish stable versions published under next from ordinary RCs.

Useful? React with 👍 / 👎.

lockfiles pin exact versions, so `npm ci` is unaffected.
Loading