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
38 changes: 34 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,38 +31,65 @@ jobs:
- name: format
node: true
cargo: false
nix_shell: .#default
command: pnpm run fmt -- --check

- name: rust lint
node: false
cargo: true
nix_shell: .#default
command: |
cargo clippy --locked -p fastsse -p fastsse-node --all-targets -- -D warnings
cargo clippy --locked -p fastsse-wasm --target wasm32-unknown-unknown -- -D warnings

- name: rust check
node: false
cargo: true
nix_shell: .#default
command: |
cargo check --locked -p fastsse -p fastsse-node --all-targets
cargo check --locked -p fastsse-wasm --target wasm32-unknown-unknown

- name: rust msrv
node: false
cargo: true
nix_shell: .#msrv
command: |
echo "Checking MSRV from workspace.package.rust-version; update Cargo.toml and flake.nix together if this fails."
cargo check --locked -p fastsse -p fastsse-node --all-targets
cargo check --locked -p fastsse-wasm --target wasm32-unknown-unknown

- name: rust test
node: false
cargo: true
nix_shell: .#default
command: |
cargo test --locked -p fastsse
cargo test --locked -p fastsse-node

- name: supply-chain audit
node: true
cargo: true
nix_shell: .#default
command: pnpm run audit:supply-chain

- name: package dry-run
node: true
cargo: true
nix_shell: .#default
command: pnpm run package:dry-run

- name: node package
node: true
cargo: true
command: pnpm run build:node
nix_shell: .#default
command: pnpm run package:node

- name: wasm package
node: true
cargo: true
command: pnpm run build:wasm
nix_shell: .#default
command: pnpm run package:wasm
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
Expand All @@ -79,11 +106,14 @@ jobs:

- name: Fetch Rust dependencies
if: ${{ matrix.cargo }}
env:
NIX_SHELL: ${{ matrix.nix_shell }}
run: |
nix develop --command cargo fetch --locked
nix develop "$NIX_SHELL" --command cargo fetch --locked

- name: Run ${{ matrix.name }}
env:
CHECK_COMMAND: ${{ matrix.command }}
NIX_SHELL: ${{ matrix.nix_shell }}
run: |
nix develop --command bash -e -u -o pipefail -c "$CHECK_COMMAND"
nix develop "$NIX_SHELL" --command bash -e -u -o pipefail -c "$CHECK_COMMAND"
200 changes: 200 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
name: release

on:
push:
tags:
- "v*.*.*"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false

env:
CARGO_TERM_COLOR: always
RUST_BACKTRACE: "1"

jobs:
verify:
name: release / verify packages
runs-on: ubuntu-24.04
timeout-minutes: 45
permissions:
attestations: write
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22

- name: Install npm dependencies
run: |
nix develop .#default --command pnpm install --frozen-lockfile

- name: Fetch Rust dependencies
run: |
nix develop .#default --command cargo fetch --locked

- name: Verify release tag
run: |
nix develop .#default --command node scripts/verify-release-tag.mjs

- name: Run release gates
run: |
nix develop .#default --command bash -e -u -o pipefail -c '
pnpm run audit:supply-chain
pnpm run package:dry-run
'

- name: Build release artifacts
run: |
nix develop .#default --command bash -e -u -o pipefail -c '
mkdir -p dist/npm
pnpm run package:crates
pnpm run build:node
pnpm run build:wasm
node scripts/sync-wasm-package.mjs
npm pack ./npm/fastsse_node --pack-destination dist/npm
npm pack ./npm/fastsse_wasm --pack-destination dist/npm
'

- name: Upload release artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-packages
if-no-files-found: error
path: |
dist/npm/*.tgz
target/package/*.crate

- name: Attest release artifacts
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-path: |
dist/npm/*.tgz
target/package/*.crate

publish-crates:
name: release / publish crates.io
runs-on: ubuntu-24.04
timeout-minutes: 45
needs: verify
environment: crates-io
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22

- name: Fetch Rust dependencies
run: |
nix develop .#default --command cargo fetch --locked

- name: Authenticate to crates.io
id: crates-io
uses: rust-lang/crates-io-auth-action@bbd81622f20ce9e2dd9622e3218b975523e45bbe # v1.0.4

- name: Publish crates
env:
CARGO_REGISTRY_TOKEN: ${{ steps.crates-io.outputs.token }}
run: |
nix develop .#default --command bash -e -u -o pipefail <<'BASH'
version=$(node -e "const fs=require(\"node:fs\"); const toml=fs.readFileSync(\"Cargo.toml\", \"utf8\"); console.log(toml.match(/^version = \"([^\"]+)\"$/m)[1]);")

crate_is_published() {
node scripts/crate-version-published.mjs "$1" "$version"
}

wait_for_crate() {
crate="$1"
for attempt in $(seq 1 30); do
if crate_is_published "$crate"; then
echo "$crate@$version is visible on crates.io"
return 0
fi
echo "waiting for $crate@$version to appear on crates.io ($attempt/30)"
sleep 20
done
echo "$crate@$version did not appear on crates.io in time" >&2
return 1
}

publish_crate() {
crate="$1"
if crate_is_published "$crate"; then
echo "$crate@$version is already published; skipping"
else
cargo publish --locked -p "$crate"
fi
wait_for_crate "$crate"
}

publish_crate fastsse
publish_crate fastsse-node
publish_crate fastsse-wasm
BASH

publish-npm:
name: release / publish npm
runs-on: ubuntu-24.04
timeout-minutes: 30
needs: verify
environment: npm
permissions:
contents: read
id-token: write
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- name: Install Nix
uses: DeterminateSystems/nix-installer-action@ef8a148080ab6020fd15196c2084a2eea5ff2d25 # v22

- name: Install npm dependencies
run: |
nix develop .#default --command pnpm install --frozen-lockfile

- name: Build npm packages
run: |
nix develop .#default --command bash -e -u -o pipefail -c '
pnpm run build:node
pnpm run build:wasm
node scripts/sync-wasm-package.mjs
'

- name: Publish npm packages
env:
NPM_CONFIG_ACCESS: public
NPM_CONFIG_PROVENANCE: "true"
run: |
nix develop .#default --command bash -e -u -o pipefail <<'BASH'
publish_package() {
name="$1"
dir="$2"
version=$(node -e "console.log(JSON.parse(require(\"node:fs\").readFileSync(\"$dir/package.json\", \"utf8\")).version)")

if npm view "$name@$version" version >/dev/null 2>&1; then
echo "$name@$version is already published; skipping"
else
npm publish "$dir" --access public --provenance
fi
}

publish_package @matesinc/fastsse-node ./npm/fastsse_node
publish_package @matesinc/fastsse-wasm ./npm/fastsse_wasm
BASH
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ resolver = "3"

[workspace.package]
authors = ["Mates Inc."]
categories = ["encoding", "parser-implementations", "web-programming"]
edition = "2024"
homepage = "https://github.com/matesedu/fastsse"
keywords = ["eventsource", "parser", "server-sent-events", "sse", "streaming"]
license = "GPL-3.0-or-later"
repository = "https://github.com/matesedu/fastsse"
rust-version = "1.88"
Expand All @@ -14,6 +17,7 @@ version = "0.1.0"
[workspace.dependencies]
compact_str = "0.9.0"
criterion = "0.8.2"
fastsse = { path = "crates/fastsse", version = "=0.1.0" }
insta = "1.46.3"
js-sys = "0.3.91"
memchr = "2.8.0"
Expand Down
58 changes: 56 additions & 2 deletions RELEASE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ vp run check
vp run test
vp run build:node
vp run build:wasm
vp run audit:supply-chain
vp run package:dry-run
```

Inspect package contents before publishing:

```bash
pnpm --dir npm/fastsse_node pack --dry-run
pnpm --dir npm/fastsse_wasm pack --dry-run
pnpm run package:dry-run
```

Create the release bump and annotated tag:
Expand All @@ -49,3 +50,56 @@ vp run release:patch
```

Push the release commit and tag together after review.

## Supply-chain Policy

Release checks run both advisory and license gates:

```bash
pnpm run audit:supply-chain
```

This runs `cargo deny check`, `pnpm audit --audit-level moderate`, and the npm
license allowlist in `scripts/audit-npm-licenses.mjs`.

Maintain exceptions deliberately:

- Rust advisory exceptions belong in `deny.toml` under `[advisories].ignore`.
Include the advisory ID, a short comment in the reviewing PR, and a follow-up
issue for removal.
- Rust license exceptions belong in `deny.toml` under `[licenses].exceptions`
only after confirming the package, version range, and redistribution impact.
- npm license exceptions belong in `scripts/audit-npm-licenses.mjs` only after
maintainers confirm the license terms are compatible with publishing public
npm artifacts.

## MSRV

The minimum supported Rust version is declared in `Cargo.toml` and pinned in the
`msrv` Nix shell. CI keeps latest-stable checks and also runs:

```bash
nix develop .#msrv --command cargo check --locked -p fastsse -p fastsse-node --all-targets
nix develop .#msrv --command cargo check --locked -p fastsse-wasm --target wasm32-unknown-unknown
```

When bumping MSRV, update `workspace.package.rust-version`, `rust-toolchain.toml`,
and `flake.nix` in the same change.

## Trusted Publishing

Tags matching `v*.*.*` run `.github/workflows/release.yml`.

Before the first trusted release, publish the initial crate/package versions
manually if the registry requires bootstrapping, then configure trusted
publishers for:

- crates.io: `fastsse`, `fastsse-node`, and `fastsse-wasm`, restricted to the
`release.yml` workflow and the `crates-io` environment.
- npm: `@matesinc/fastsse-node` and `@matesinc/fastsse-wasm`, restricted to the
`release.yml` workflow and the `npm` environment.

The release workflow uses GitHub OIDC instead of long-lived publish tokens,
performs crate and npm package dry-runs before publishing, uploads package
artifacts, and generates GitHub build provenance attestations for the `.crate`
and `.tgz` files.
Loading
Loading