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
9 changes: 0 additions & 9 deletions .github/release.yml

This file was deleted.

31 changes: 22 additions & 9 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,22 +23,28 @@ name: CI
# publish of those exact tarballs, and the verify of what was published all
# passed for that commit.
#
# - Stable (`latest` dist-tag): ONLY when a GitHub Release is published.
# The tag IS the version (vX.Y.Z -> X.Y.Z); nothing is committed to the
# repo, package.json carries a placeholder. `make release V=X.Y.Z`.
# A tag with a prerelease part (v1.0.0-rc.1) ships under `next`.
# - Stable (`latest` dist-tag): ONLY for a vX.Y.Z tag. The tag IS the
# version (vX.Y.Z -> X.Y.Z); the three publishable package.json files
# carry a placeholder that is stamped at build time. release-please.yml
# owns the tag: merging its "chore(release): X.Y.Z" PR (`make release`)
# creates the tag + GitHub Release and dispatches this workflow at the
# tag with `release_tag` (a dispatch is needed because a release created
# by the workflow token never fires the `release:` trigger). The
# `release:` trigger is kept for hand-cut tags (`make release-rc`); a tag
# with a prerelease part (v1.0.0-rc.1) ships under `next`.
# Ships only once the commit's own push-to-main run is green: the gate
# below waits for an in-flight main run and fails on a red one, and
# release-retry.yml re-runs the failed Publish as soon as main goes
# green - so `make release` is fire-and-forget.
# green - so a release is fire-and-forget.
# - Prerelease (`next` dist-tag): every push to main (or a manual
# dispatch) ships <next-patch-after-latest-tag>-pre.<run>.<sha>.
# dispatch without `release_tag`) ships
# <next-patch-after-latest-tag>-pre.<run>.<sha>.
# Either way the three packages get the same version and the platform
# packages pin core to exactly that version (the only range that also
# works for prereleases).
#
# Requires no extra secrets - GITHUB_TOKEN with packages:write is enough
# for same-org publishing.
# for same-org publishing, and release-please.yml needs only the same token.
#
# iOS E2E runs by default: the repo is public, so GitHub-hosted macOS minutes
# are free (they bill at 10x on private repos, which is why the job used to be
Expand All @@ -61,8 +67,14 @@ on:
pull_request:
types: [opened, synchronize, reopened, labeled] # labeled: the `e2e:ios` override
release:
types: [published]
types: [published] # hand-cut tags only (make release-rc); see release-please.yml
workflow_dispatch:
inputs:
release_tag:
description: 'Ship this vX.Y.Z tag under `latest` (set by release-please.yml; run at the tag ref). Empty = prerelease from HEAD.'
type: string
required: false
default: ''

# A registry request that hangs costs one minute per attempt instead of
# npm's default five (#52). Warm installs make no request at all.
Expand Down Expand Up @@ -99,6 +111,7 @@ jobs:
with:
ios: ${{ vars.E2E_IOS != 'false' || contains(github.event.pull_request.labels.*.name, 'e2e:ios') }}
ios-runner: ${{ vars.E2E_IOS_RUNNER || 'macos-latest' }}
release-tag: ${{ inputs.release_tag || '' }}

# Badges for this branch under gh-pages/badges/<branch>/: the measured
# coverage badge (artifact from Unit / Coverage, or a red "failing"
Expand Down Expand Up @@ -192,7 +205,7 @@ jobs:
# ship over a red one: wait while it is still running, fail if it
# failed. release-retry.yml re-runs this job once main turns green.
- name: Require a green main run for the release commit
if: github.event_name == 'release'
if: github.event_name == 'release' || inputs.release_tag != ''
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ on:
description: runs-on label(s) for the iOS job (string or JSON array)
type: string
default: macos-latest
release-tag:
description: vX.Y.Z tag this run releases (release-please dispatch); empty = not a release
type: string
default: ''
outputs:
version:
description: The version stamped into the packages built by Build Packages (what Publish ships)
Expand Down Expand Up @@ -110,11 +114,13 @@ jobs:

# Stamped before the build so the tarballs carry the version they ship
# under. On a PR this is a -pre.<run>.<sha> that is never published.
# A dispatch carrying `release_tag` (release-please.yml, run at the tag
# ref) is a release: the tag is the version.
- name: Resolve version and stamp it into the packages
id: ver
env:
EVENT: ${{ github.event_name }}
TAG: ${{ github.event.release.tag_name }}
EVENT: ${{ inputs.release-tag != '' && 'release' || github.event_name }}
TAG: ${{ inputs.release-tag || github.event.release.tag_name }}
RUN: ${{ github.run_number }}
run: bash scripts/release/resolve-version.sh # tag = version on release; -pre.<run>.<sha> otherwise

Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Release Please

# Owns the version, CHANGELOG.md, the vX.Y.Z tag and the GitHub Release.
# On every push to main it reads the Conventional Commits since the last
# release (the squashed PR titles commitlint enforces), infers the bump
# (feat -> minor, fix -> patch, `!` / BREAKING CHANGE -> minor while < 1.0)
# and keeps ONE open pull request, "chore(release): X.Y.Z", up to date with
# the grouped changelog entry and the root package.json version. Nothing
# happens until a human merges that PR; merging it is the whole release
# step (`make release`). ci/docs/chore commits alone never open one.
#
# Merging the release PR lands on main, this workflow runs again, sees the
# merged release PR, creates the tag + GitHub Release (body = the changelog
# section), and then starts the release run of ci.yml at that tag. The
# explicit dispatch is needed because GitHub never triggers workflows from
# events the workflow token itself created - a release made here would not
# fire ci.yml's `release:` trigger. ci.yml treats a dispatch with
# `release_tag` exactly like a `release` event (tag = version, `latest`),
# including the green-main gate and release-retry.yml. No extra secrets.
#
# The same token rule means CI does not run on the release PR itself. It
# only touches CHANGELOG.md, the manifest and the root package.json /
# package-lock.json; the tagged run executes the full pipeline before
# anything ships.
on:
push:
branches: [main]

permissions:
contents: write # tag + release + the release branch
pull-requests: write # the release PR
actions: write # dispatch ci.yml at the tag

concurrency:
group: release-please
cancel-in-progress: false

jobs:
release-please:
name: Release PR / Tag
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Update the release PR, or tag the merged one
id: release
uses: googleapis/release-please-action@v5

- name: Start the release run of ci.yml at the new tag
if: steps.release.outputs.release_created == 'true'
env:
GH_TOKEN: ${{ github.token }}
REPO: ${{ github.repository }}
TAG: ${{ steps.release.outputs.tag_name }}
run: gh workflow run ci.yml --repo "$REPO" --ref "$TAG" -f "release_tag=$TAG"
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.2.0"
}
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ one-line description. The ones you will reach for:
| `make db-up migrate backend` | Dev Postgres, migrations, backend dev server |
| `make e2e-backend` / `make e2e-web` | Backend E2E against real Postgres / Playwright browser E2E |
| `make start` / `make ios` / `make android` / `make web` | Demo apps |
| `make release V=X.Y.Z` | Cut a stable release (the tag is the version; nothing is committed) |
| `make release` | Merge the open release PR that release-please maintains (tags, publishes; `docs/releasing.md`) |

Underlying npm scripts (`npm test`, `npm run typecheck`, `npm run lint`,
`npm run build`, ...) are listed in CLAUDE.md.
Expand Down
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Changelog

Maintained by [release-please](https://github.com/googleapis/release-please)
from Conventional Commit PR titles; see [docs/releasing.md](docs/releasing.md).
The two entries below were written by hand from the v0.1.0 and v0.2.0 GitHub
Releases in the same format release-please prepends to.

## [0.2.0](https://github.com/blinkbitcoin/esign/compare/v0.1.0...v0.2.0) (2026-09-04)

Two new integration paths next to the drop-in component, in both `@blinkbitcoin/esign-react` and `@blinkbitcoin/esign-react-native`. No breaking changes; all existing props and testIDs keep working. `@blinkbitcoin/esign-core` is republished unchanged so the three packages stay in lockstep.

### Features

* headless `useESignature` hook and `ESignature` theming (`theme`, `styles`, `labels`) for RN and web ([#60](https://github.com/blinkbitcoin/esign/issues/60)) ([a990939](https://github.com/blinkbitcoin/esign/commit/a99093979805f09b58ab0a651c69c4abc13d7511))

### Bug Fixes

* resolve the CodeQL security-and-quality alerts ([#55](https://github.com/blinkbitcoin/esign/issues/55)) ([58adb33](https://github.com/blinkbitcoin/esign/commit/58adb33f20f79d3388997a6681f1545a51923b33))
* **api:** suppress false-positive CodeQL password-hash alert on JWT secret ([b4496dd](https://github.com/blinkbitcoin/esign/commit/b4496dd67311e56fe39421d25aabf2ea704fc9f0))
* **demo:** regenerate Podfile.lock for react-native 0.86.3 ([#51](https://github.com/blinkbitcoin/esign/issues/51)) ([9249130](https://github.com/blinkbitcoin/esign/commit/924913070c8252b36b2940d2a065a5a927d7dbcf))

## [0.1.0](https://github.com/blinkbitcoin/esign/releases/tag/v0.1.0) (2026-09-02)

Initial public feature set: provider-agnostic `ESignature` component (React Native WebView + React web iframe/DocuSign.js) over a shared `SigningSource` core with three modes: public URL, DocuSign Web Forms instances, and proxy envelopes (Apollo). Apollo-free `/webform` subpath entries for minimal Web Forms-only consumers.

Packages (GitHub Packages, `@blinkbitcoin` scope): `esign-core`, `esign-react-native`, `esign-react`, all `0.1.0`.
13 changes: 8 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ The ones that matter most: `make test` (unit + check-code), `make coverage`,
E2E → teardown), `make e2e-web[-webform|-publicurl]` (Playwright), `make
e2e-android` / `make e2e-ios` (Maestro, needs a running stack; `make
e2e-backend-up` starts the mock-provider backend), `make db-up/migrate/backend`,
`make ios/android/start/web`, `make pods`, `make build`, `make release V=X.Y.Z`,
`make ios/android/start/web`, `make pods`, `make build`, `make release`,
`make clean/reset`. The underlying npm scripts:

```bash
Expand Down Expand Up @@ -175,10 +175,13 @@ rm -rf node_modules package-lock.json && npm install # Full reinstall (root loc
sees plus `android/**` / `ios/**`; bump the cache key's `v` suffix when an
input the script cannot see changes.
- Releases: prerelease (`next`) on every green push to `main`; stable is
`make release V=X.Y.Z` - the tag is the version, CI stamps it at publish
time, `package.json` stays at `0.0.0-development`. Release notes come from
PR titles (`.github/release.yml`). A release ships only once the commit's
main run is green (`release-retry.yml` re-runs a blocked Publish).
merging the `chore(release): X.Y.Z` PR that release-please opens once a
feat/fix lands (`make release`). That tags `vX.Y.Z`, writes the GitHub
Release from `CHANGELOG.md`, and dispatches `ci.yml` at the tag - the tag
is the version, CI stamps it at publish time, the three package.json files
stay at `0.0.0-development`. Never hand-edit CHANGELOG.md or the root
`package.json` version. A release ships only once the commit's main run is
green (`release-retry.yml` re-runs a blocked Publish). `docs/releasing.md`.

## Architecture Patterns

Expand Down
31 changes: 20 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ area names (`commitlint.config.mjs` is the source of truth):
| `ci` | `.github/` |
| `deps`, `deps-dev` | Dependency bumps (Dependabot uses these) |
| `docs` | `docs/` when the type is not already `docs` |
| `release` | Release tooling (workflow, notes config) |
| `release` | Release tooling (release-please config and workflow); release-please's own PRs are `chore(release): X.Y.Z` |

Examples:

Expand Down Expand Up @@ -122,22 +122,31 @@ Escape hatches, for the rare cases where they are warranted:
2. Diagrams: edit `docs/diagrams/src/*.mmd`, then `make diagrams` (CI fails
on drift). Schema: edit `apps/api/src/typeDefs.ts`, then `make codegen`.
3. Open a PR with a Conventional Commits title — every workflow must be
green. The title is also the line the release notes will show.
green. The title is the line `CHANGELOG.md` will show, and its type
decides the version bump (`feat` → minor, `fix` → patch, `ci` / `docs` /
`chore` → none), so a fix that only touches CI or the demos is `ci:` or
`chore(demo):`, not `fix(ci):`.

## Releases

Full walkthrough with a worked example: [docs/releasing.md](docs/releasing.md).

- **Prerelease** (`next` tag): automatic on every green push to `main`,
versioned `<next patch after the latest tag>-pre.<run>.<sha>`.
- **Stable**: one step — `make release V=X.Y.Z` (wraps
`gh release create vX.Y.Z --target main --generate-notes`). **The tag is
the version**: CI stamps it into the three packages before building them, so
nothing is committed and `package.json` stays at `0.0.0-development`.
The release notes, generated from PR titles (`.github/release.yml`), are
the changelog. A tag with a prerelease part (`v1.0.0-rc.1`) ships under
`next`. GitHub Packages never accepts the same version twice, so a failed
release means fixing forward and cutting a new tag.
- **Stable**: merge the `chore(release): X.Y.Z` pull request. release-please
opens it once a `feat` / `fix` / `perf` / `revert` commit reaches `main`
and keeps it current: the inferred version, the `CHANGELOG.md` entry
grouped by type (CI, docs and chores are left out), the root
`package.json` version. Approve it and `make release` (or the Merge
button). Merging tags `vX.Y.Z`, publishes the GitHub Release with that
entry as its body, and starts the release run. **The tag is the version**:
CI stamps it into the three packages before building them, so their
`package.json` stays at `0.0.0-development`. GitHub Packages never
accepts the same version twice, so a failed release means fixing forward.
A release ships only once the commit's push-to-`main` run is green: the
release run waits for an in-flight main run and refuses a red one, and
`release-retry.yml` re-runs the blocked Publish automatically when main
turns green (re-run the flaky job with `gh run rerun <id> --failed`).
So `make release` is fire-and-forget.
So merging the release PR is fire-and-forget.
- **Release candidate**: `make release-rc V=X.Y.Z-rc.1` hand-cuts a
prerelease-suffixed tag that ships under `next`.
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,15 @@ diagrams-check: ## Fail if docs/diagrams/README.md is stale relative to src/*.mm
docs-check: ## Warn when architecture-relevant changes (vs origin/main) ship without a docs/ update; fail on stale diagram SVGs
bash scripts/ci/docs-freshness.sh

release: ## Cut a stable release: make release V=X.Y.Z (the tag is the version; CI publishes @latest)
@test -n "$(V)" || { echo "usage: make release V=X.Y.Z"; exit 1; }
gh release create "v$(V)" --target main --title "v$(V)" --generate-notes
release: ## Merge the open release PR (release-please opens it after a feat/fix lands on main); needs one approval first
@pr=$$(gh pr list --state open --label 'autorelease: pending' --json number,title -q '.[0] | "\(.number) \(.title)"'); \
test -n "$$pr" || { echo "no open release PR: one appears after a feat/fix/perf commit reaches main (docs/releasing.md)"; exit 1; }; \
echo "merging #$$pr"; gh pr merge "$${pr%% *}" --squash

release-rc: ## Hand-cut a prerelease-suffixed tag that ships under next: make release-rc V=X.Y.Z-rc.1 (stable releases: make release)
@test -n "$(V)" || { echo "usage: make release-rc V=X.Y.Z-rc.1"; exit 1; }
@case "$(V)" in *-*) ;; *) echo "V must carry a prerelease suffix (X.Y.Z-rc.1); stable versions go through the release PR"; exit 1 ;; esac
gh release create "v$(V)" --target main --title "v$(V)" --prerelease --notes "Prerelease $(V) - see CHANGELOG.md on main for the pending changes."

version: ## Show what CI would publish for HEAD (prerelease), or for a tag: make version TAG=vX.Y.Z
@DRY_RUN=1 EVENT=$(if $(TAG),release,push) TAG=$(TAG) bash scripts/release/resolve-version.sh
Expand Down Expand Up @@ -171,7 +177,7 @@ help: ## List available targets
@grep -hE '^[a-zA-Z0-9_-]+:.*##' $(MAKEFILE_LIST) | \
awk 'BEGIN {FS = ":.*##"} {printf " \033[36m%-15s\033[0m %s\n", $$1, $$2}'

.PHONY: install hooks pods release version registry-smoke unit coverage coverage-badge typecheck lint format format-check check-code \
.PHONY: install hooks pods release release-rc version registry-smoke unit coverage coverage-badge typecheck lint format format-check check-code \
shellcheck check-ci codegen-check test build codegen diagrams-check docs-check start ios android backend web db-up db-down migrate \
diagrams test-db-up test-db-down e2e-backend e2e-web e2e-web-webform e2e-web-publicurl \
e2e-backend-up e2e-backend-down ios-build e2e-ios e2e-android test-live clean reset help
Loading