Skip to content
Open
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
180 changes: 180 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ on:
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
strategy:
matrix:
node-version: [20, 22, 24]
Expand All @@ -27,6 +30,183 @@ jobs:
- run: pnpm api:check
- run: pnpm test

entrypoints:
name: Package entry point smoke tests
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
- run: pnpm test:exports

differential:
name: Differential (test-vectors vs vN-1)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dorny/paths-filter@v3
id: filter
if: github.event_name == 'pull_request'
with:
filters: |
chains:
- 'src/chains/**'

- name: Determine whether to run
id: should-run
run: |
if [ "${{ github.event_name }}" != "pull_request" ] || [ "${{ steps.filter.outputs.chains }}" = "true" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
fi

- uses: pnpm/action-setup@v4
if: steps.should-run.outputs.run == 'true'
with:
version: 10

- uses: actions/setup-node@v4
if: steps.should-run.outputs.run == 'true'
with:
node-version: 22
cache: pnpm

- name: Install dependencies
if: steps.should-run.outputs.run == 'true'
run: pnpm install --frozen-lockfile

- name: Build SDK
if: steps.should-run.outputs.run == 'true'
run: pnpm build

- name: Run differential harness
if: steps.should-run.outputs.run == 'true'
run: pnpm --filter @wraith-protocol/test-vectors differential

bundle-size:
name: Bundle size (size-limit)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: dorny/paths-filter@v3
id: filter
if: github.event_name == 'pull_request'
with:
filters: |
src:
- 'src/**'

- name: Determine whether to run
id: should-run
run: |
if [ "${{ github.event_name }}" != "pull_request" ] || [ "${{ steps.filter.outputs.src }}" = "true" ]; then
echo "run=true" >> "$GITHUB_OUTPUT"
else
echo "run=false" >> "$GITHUB_OUTPUT"
fi

- uses: pnpm/action-setup@v4
if: steps.should-run.outputs.run == 'true'
with:
version: 10

- uses: actions/setup-node@v4
if: steps.should-run.outputs.run == 'true'
with:
node-version: 22
cache: pnpm

- name: Install dependencies
if: steps.should-run.outputs.run == 'true'
run: pnpm install --frozen-lockfile

- name: Build SDK
if: steps.should-run.outputs.run == 'true'
run: pnpm build

- name: Check bundle sizes
if: steps.should-run.outputs.run == 'true'
run: pnpm size

slow-tests:
name: Property fuzz (nightly)
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm test:fuzz

heap-regression:
name: Heap constructor regression (nightly)
runs-on: ubuntu-latest
if: github.event_name == 'schedule'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Run constructor-level heap regression harness
run: pnpm test:heap-leak
- name: Upload heap regression artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: heap-regression-${{ github.run_id }}
path: |
heap-diff.json
heap-before.heapsnapshot
heap-after.heapsnapshot
if-no-files-found: warn
retention-days: 14

release:
name: Pack + release alignment
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm build
# What `pnpm publish` would ship: only dist/ plus the files npm always
# includes. Catches a stray `files` edit at PR time rather than release time.
- name: Verify the packed file list
run: pnpm pack:check
# Changelog entry and API report alignment for the current version.
- name: Check release alignment
run: pnpm release:check

entrypoints:
name: Package entry point smoke tests
runs-on: ubuntu-latest
Expand Down
26 changes: 24 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ jobs:
- run: pnpm build
- run: pnpm test

# Release alignment: the changelog entry for this version and the API
# report matching the built declarations, before anything is published.
- name: Check release alignment
run: pnpm release:check

# Prove the tarball contains only what `files` intends. Runs the real
# `pnpm pack --dry-run`, so a stray source, test, config or API-report path
# fails the release instead of reaching every consumer.
- name: Verify the packed file list
run: pnpm pack:check

- name: Check if version is already published
id: check
run: |
Expand All @@ -47,8 +58,19 @@ jobs:
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

- name: Publish to npm
# --provenance asks the registry to attach a signed attestation linking the
# published tarball to this workflow run. It requires the "id-token: write"
# permission declared above and a public repository.
- name: Publish to npm with provenance
if: steps.check.outputs.exists == 'false'
run: pnpm publish --access public --no-git-checks --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}

# Publishing with the flag is not the same as having an attestation: assert
# the registry actually attached one before this run reports success.
- name: Verify the provenance attestation
if: steps.check.outputs.exists == 'false'
run: pnpm publish --access public --no-git-checks
run: node scripts/verify-provenance.mjs
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
128 changes: 128 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Releasing the SDK

This is the checklist for cutting a release of `@wraith-protocol/sdk`. It exists
because four things have to agree before a version goes to npm — the version, the
changelog, the API report, and the tarball contents — and three of them are
enforced by CI so they cannot be skipped by accident.

Publishing is automated: `.github/workflows/publish.yml` runs on every push to
`main` that touches `package.json`, builds, tests, verifies, and publishes with
provenance if the version is not already on the registry. The checklist below is
what must be true in the commit you push.

## Before you bump

- [ ] `main` is green, including the **Pack + release alignment** job.
- [ ] `pnpm install --frozen-lockfile && pnpm build && pnpm test` passes locally.
- [ ] `pnpm api:check` passes. If the public API changed, the refreshed
`etc/*.api.md` reports are part of this release commit, not a follow-up —
`pnpm release:check` fails if a configured report is missing.

## 1. Version

- [ ] Decide the bump (semver: breaking → major, additive → minor, fixes → patch).
`MIGRATING.md` documents the breaking-change process and must be updated for
a major.
- [ ] Run `pnpm version <major|minor|patch> --no-git-tag-version` (or edit
`package.json` directly) so the `version` field is the version you are
publishing.
- [ ] Confirm the version is not already on the registry:

```bash
npm view @wraith-protocol/sdk@<version> version # should print nothing
```

The publish workflow performs the same check and skips publishing if the
version exists, so a repeated push is harmless but also does nothing.

## 2. Changelog

- [ ] `pnpm release:check` reports no warning about the version you are
releasing. It warns when `package.json` names a version with no
corresponding `## [<version>]` heading.
- [ ] In `CHANGELOG.md`, move the relevant entries out of `## Upcoming: <version>`
into a released heading:

```markdown
## [1.6.0] - 2026-09-25
```

- [ ] Every user-visible change is listed, and breaking changes say so
explicitly, with a `MIGRATING.md` link.
- [ ] Reference issue numbers the way the existing entries do
(`(issue #210)`), so the release notes stay traceable.

The `release` job in CI enforces the structure: a released heading must exist, and
a version in `package.json` with no heading is reported as a warning on every PR
so it is visible long before the release.

## 3. API report alignment

- [ ] `etc/sdk.api.md` plus the chain-specific reports (`sdk-ckb`, `sdk-evm`,
`sdk-solana`, `sdk-stellar`, `sdk-vault`) are committed and current.
- [ ] `pnpm api:check` is clean against the built `dist/` — `release:check`
confirms a report exists for every `api-extractor*.json` configuration, and
the CI `test` job runs `api:check` itself.
- [ ] If you added or removed an entry point, the corresponding
`api-extractor-*.json` and report are both in the commit.

A report that exists but is stale is still caught: `api:check` compares the
report against the freshly built declarations and fails the build on a mismatch.

## 4. Pack contents

- [ ] `pnpm pack:check` passes. It runs the real `pnpm pack --dry-run` and fails
if anything outside `dist/` (plus the files npm always includes:
`package.json`, `README`, `LICENSE`, `CHANGELOG`) would ship — source,
tests, `docs/`, `scripts/`, `etc/` API reports, `api-extractor*.json`,
lockfiles or CI config.
- [ ] If the package layout intentionally changed, update `files` in
`package.json` and the allowlist in `scripts/verify-pack.mjs` in the same
commit, and say why in the PR.
- [ ] To see the list locally:

```bash
pnpm build && pnpm pack:check
```

## 5. Publish

Push the release commit to `main`. The workflow then:

1. installs, builds and tests;
2. runs `pnpm release:check` (version/changelog/API report alignment);
3. runs `pnpm pack:check` (packed file list);
4. checks the registry and skips if the version already exists;
5. publishes with `pnpm publish --access public --no-git-checks --provenance`;
6. runs `node scripts/verify-provenance.mjs`, which reads the registry metadata
back and fails the run if no attestation was attached.

## 6. After publishing

- [ ] Confirm the run's **Verify the provenance attestation** step reported a
predicate type and URL. `--provenance` silently doing nothing (a dropped
`id-token: write`, a private repo) is exactly what that step catches.
- [ ] Confirm the attestation on the registry:

```bash
npm view @wraith-protocol/sdk@<version> dist.attestations --json
```

- [ ] Spot-check the published tarball matches the local pack list:

```bash
npm view @wraith-protocol/sdk@<version> dist.tarball
```
- [ ] Open the `## Upcoming` section again for the next cycle, and revert this
checklist mentally to "before you bump".

## If something is wrong after publish

npm does not allow re-using a version number, so a broken release is fixed
forward, never by republishing the same version.

1. `npm deprecate '@wraith-protocol/sdk@<version>' "<reason and the fixed version>"`.
2. If the tarball is unusable, `npm unpublish '@wraith-protocol/sdk@<version>'`
within the registry's 72-hour window, then release again with an incremented
version. Prefer deprecation — unpublishing breaks every lockfile pinned to it.
3. Add the postmortem to the changelog entry for the fixing release.
Loading
Loading