Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
f36c871
chore: add release runbook, hardening plan, and commit-lint
Kyleasmth Jun 23, 2026
a632f78
fix: pin commitlint to v19 for Node 20 compatibility
Kyleasmth Jun 25, 2026
f0c6ada
ci: anchor commitlint range at merge-base with full main fetch
Kyleasmth Jun 26, 2026
a8b7376
fix: use per-package tags in runbook and tighten release-commit ignore
Kyleasmth Jun 26, 2026
70ff2cc
Merge branch 'main' into ks/YPE-2486-release-hardening
jhampton Jun 29, 2026
854cb99
docs: add open-decisions record for release hardening
Kyleasmth Jun 30, 2026
11c1080
ci: enforce conventional PR titles, drop commitlint ticket prefix
Kyleasmth Jul 20, 2026
4747b00
chore: run git hooks via corepack pnpm with a pnpm fallback
Kyleasmth Jul 20, 2026
448c0da
ci: require a changeset on every PR
Kyleasmth Jul 20, 2026
2e46be2
ci: document the intentional per-workflow Node version split
Kyleasmth Jul 20, 2026
bf5512a
docs: record release-hardening decisions and add greptile/AGENTS guar…
Kyleasmth Jul 20, 2026
a379ee7
chore: add empty changeset for release-hardening (no release)
Kyleasmth Jul 20, 2026
3c348c6
docs: record Decision 3 Node floor at 22.13, resolved by main's pnpm …
Kyleasmth Jul 20, 2026
045cd27
Merge origin/main; align release-hardening to pnpm 11 / Node 24 (floo…
Kyleasmth Jul 20, 2026
7f7ece4
ci: ignore merge commits in commitlint config
Kyleasmth Jul 20, 2026
c0cd9c6
Merge branch 'main' into ks/YPE-2486-release-hardening
jhampton Jul 21, 2026
3131679
Merge remote-tracking branch 'origin/main' into ks/YPE-2486-release-h…
Kyleasmth Jul 21, 2026
97d82b7
Merge branch 'main' into ks/YPE-2486-release-hardening
jhampton Jul 22, 2026
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
6 changes: 6 additions & 0 deletions .changeset/release-hardening.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
---

Release-process hardening: CI guardrails (conventional PR titles, required
changesets), corepack-aware git hooks, documented Node policy, and review
rules. No package changes — intentionally empty (no release).
45 changes: 45 additions & 0 deletions .github/workflows/changeset.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Changeset

on:
pull_request:
types: [opened, synchronize, reopened]

permissions:
contents: read

jobs:
require-changeset:
name: Require Changeset
runs-on: ubuntu-latest
# The Changesets "version packages" PR (branch changeset-release/main)
# consumes changesets and legitimately contains none — exempt it.
if: github.head_ref != 'changeset-release/main'
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

# Fail the PR unless it adds a changeset. Every PR needs one; for a genuine
# no-release change (CI, docs, tooling) add an intentional empty changeset
# with `pnpm changeset --empty`, which still writes a .changeset/*.md file
# and satisfies this gate. See docs/release-hardening-decisions.md (Decision 4).
#
# We check for an added changeset file directly rather than using
# `changeset status --since`: in the pinned @changesets/cli that command
# exits 0 even when no changeset exists, so it cannot gate.
- name: Require a changeset
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git fetch --no-tags origin main:refs/remotes/origin/main
base=$(git merge-base origin/main "$HEAD_SHA")
changed=$(git diff --name-only --diff-filter=d "$base".."$HEAD_SHA" -- .changeset \
| grep -E '\.changeset/.+\.md$' \
| grep -vE '\.changeset/README\.md$' || true)
if [ -z "$changed" ]; then
echo "::error::No changeset found on this PR. Run 'pnpm changeset' to describe the release, or 'pnpm changeset --empty' for a no-release change (CI/docs/tooling)."
exit 1
fi
echo "Changeset(s) detected:"
echo "$changed"
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: CI

# Node policy (see docs/release-hardening-decisions.md, Decision 3):
# CI runs Node 24 LTS across the board. The consumer floor is engines.node
# >=22.13 (pnpm 11 requires it). New dev-deps MUST support that floor, or it
# gets raised as a deliberate decision — never lowered silently to escape a
# dependency constraint.

on:
push:
branches:
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/commitlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Commitlint

on:
pull_request:
types: [opened, edited, synchronize, reopened]

jobs:
commitlint:
name: Lint Commit Messages
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 24 LTS, matching the rest of CI (pnpm 11 floor is Node 22.13).
# See docs/release-hardening-decisions.md (Decision 3).
node-version: 24
cache: 'pnpm'
Comment thread
greptile-apps[bot] marked this conversation as resolved.

- name: Install dependencies
run: pnpm install --frozen-lockfile

# Lint only the commits this branch adds, using the merge-base with the
# live origin/main as the lower bound (not the PR's recorded base SHA,
# which goes stale as main moves forward). The merge-base is robust even
# when the branch is behind main: it always resolves to the point where
# this branch diverged, so the range is exactly the new commits.
#
# main MUST be fetched with full history (no --depth). A shallow fetch
# hides the merge-base, so `git log <main>..HEAD` can no longer exclude
# shared history and the range balloons to include unrelated historical
# commits. See docs/release-hardening-plan.md (AC6).
- name: Resolve lint range
id: range
env:
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
run: |
set -euo pipefail
git fetch --no-tags origin main:refs/remotes/origin/main
base=$(git merge-base origin/main "$HEAD_SHA")
echo "base=${base}" >> "$GITHUB_OUTPUT"
echo "Linting ${base}..${HEAD_SHA}"

- name: Lint commits
run: pnpm exec commitlint --from ${{ steps.range.outputs.base }} --to ${{ github.event.pull_request.head.sha }} --verbose
22 changes: 22 additions & 0 deletions .github/workflows/pr-title.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
name: PR Title

on:
pull_request:
types: [opened, edited, synchronize, reopened]

permissions:
pull-requests: read

jobs:
lint-pr-title:
name: Lint PR Title
runs-on: ubuntu-latest
steps:
# We squash-merge, so the PR title becomes the landing commit on main and
# must be Conventional Commits. Ticket references (YPE-1234) belong in the
# branch name / PR body, not the title. This is the real commit-format
# gate; the per-commit husky/commitlint hook is only a local dev aid.
# See docs/release-hardening-decisions.md (Decision 1).
- uses: amannn/action-semantic-pull-request@v6
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v6
with:
# Node 24 LTS, matching CI. Consumer floor is engines.node >=22.13
# (pnpm 11). See docs/release-hardening-decisions.md (Decision 3).
node-version: 24
cache: 'pnpm'

Expand Down
9 changes: 9 additions & 0 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Prefer corepack so the repo-pinned pnpm (packageManager in package.json) runs
# regardless of what's on PATH. Fall back to pnpm directly where corepack isn't
# available — Node 25+ no longer bundles it, and nvm/global-pnpm setups may lack
# it. See docs/release-hardening-decisions.md (Decision 2).
if command -v corepack >/dev/null 2>&1; then
corepack pnpm exec commitlint --edit "$1"
else
pnpm exec commitlint --edit "$1"
fi
10 changes: 9 additions & 1 deletion .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
pnpm lint-staged
# Prefer corepack so the repo-pinned pnpm (packageManager in package.json) runs
# regardless of what's on PATH. Fall back to pnpm directly where corepack isn't
# available — Node 25+ no longer bundles it, and nvm/global-pnpm setups may lack
# it. See docs/release-hardening-decisions.md (Decision 2).
if command -v corepack >/dev/null 2>&1; then
corepack pnpm lint-staged
else
pnpm lint-staged
fi
9 changes: 7 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,16 @@ pnpm --filter @youversion/platform-react-ui build
- Changesets required for ALL version bumps (even patches)
- **Unified versioning**: All packages must share exact same version - never version packages independently
- Pre-commit hooks fail if typecheck or lint fails
- **Every PR must include a changeset** — CI (`.github/workflows/changeset.yml`) fails a PR that adds none. For a genuine no-release change (CI/docs/tooling), add an intentional empty changeset: `pnpm changeset --empty`. A missing changeset is what caused the 2026-07-17 release failure.

### Commits & PRs
- **PR titles must be Conventional Commits** — the PR title becomes the squash-merge commit on `main` and is linted by `.github/workflows/pr-title.yml`. Ticket refs (e.g. `YPE-1234`) go in the **branch name** and PR body, not the title.
- The per-commit husky/commitlint hook is an optional local dev aid; the PR title is the real gate.

### Environment
- **Node.js requirement**: Minimum version 22.13.0 required (pnpm 11 requires Node >= 22.13); we develop and test on Node 24 LTS, which is what CI runs
- **Node.js requirement**: Minimum version 22.13.0 required (pnpm 11 requires Node >= 22.13); we develop and test on Node 24 LTS, which is what CI runs. New dev-deps must support `engines.node >=22.13`; don't lower the floor to escape a dependency constraint without a deliberate decision (see `docs/release-hardening-decisions.md`, Decision 3).
- **React version**: Do not change React dependencies; pnpm overrides (in `pnpm-workspace.yaml`) enforce 19.1.2
- **Package manager**: Do not use npm/yarn; only pnpm supported
- **Package manager**: Do not use npm/yarn; only pnpm supported. Git hooks prefer `corepack pnpm ...` (repo-pinned pnpm regardless of PATH) and fall back to `pnpm` where corepack isn't available (Node 25+ no longer bundles corepack). Keep the corepack-preferred/pnpm-fallback shape; don't hard-code bare `pnpm` only.
- **Supply-chain protection**: `minimumReleaseAge: 4320` (3-day cooldown) in `pnpm-workspace.yaml` — `pnpm install` will reject packages published < 3 days ago. Override with `--force` if needed urgently. Workspace packages (`workspace:*`) are inherently excluded as they aren't fetched from the registry.
- **pnpm 11 breaking changes**: Overrides moved from `package.json` → `pnpm-workspace.yaml`; build scripts require `allowBuilds` approval; `@internal/eslint-config` and `eslint-plugin-storybook` must be root devDependencies for resolution

Expand Down
6 changes: 6 additions & 0 deletions PUBLISHING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

This guide is for project maintainers who need to set up publishing infrastructure or troubleshoot release issues.

> **Hit something not covered here?** [`RELEASE-RUNBOOK.md`](./RELEASE-RUNBOOK.md) catalogues specific failure modes (EPUBLISHCONFLICT-after-success, transient registry 5xx, provenance attestation failure, expired `NPM_TOKEN`, OTP/2FA, `workspace:*` not rewritten, peer-dep skew, dist-tag drift) with concrete state-check and recovery commands.

## How Publishing Works

The repository uses [Changesets](https://github.com/changesets/changesets) with GitHub Actions for automated publishing.
Expand Down Expand Up @@ -53,6 +55,10 @@ Required packages:
- Automatic provenance generation
- Audit trail of all publishes

### `NPM_TOKEN` fallback (token type matters)

The workflow keeps `NPM_TOKEN` as a fallback for any package where Trusted Publishing isn't configured yet. If you set one, generate it as an **Automation token** — not a Publish or personal-user token. Automation tokens explicitly bypass npm's 2FA-on-publish, which CI cannot satisfy. A Publish token will fail every publish with `EOTP` / "need a one-time password" (see [`RELEASE-RUNBOOK.md` §5](./RELEASE-RUNBOOK.md#5-otp--2fa-error-class-wrong-token-type)). Remove `NPM_TOKEN` once all three packages are on Trusted Publishing.

## Troubleshooting

### "Version Packages" PR Not Created
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,20 @@ console.log(passage.content);
> [!NOTE]
> We are not yet accepting pull requests from external contributors, though we intend to do so in the future. In the meantime, we welcome you to use the SDK, report bugs via [GitHub Issues](https://github.com/youversion/platform-sdk-react/issues), and share feedback. See [CONTRIBUTING.md](./CONTRIBUTING.md) for more details.

### Package manager

This is a pnpm workspace — the `workspace:*` dependencies between packages are a pnpm feature, so npm and yarn are not supported. The required pnpm version is pinned in `package.json` via `packageManager` (and `engines.pnpm`).

The Git hooks prefer [Corepack](https://nodejs.org/api/corepack.html) (`corepack pnpm ...`) so a newer global pnpm on your PATH can't change how commits are linted or staged files are formatted, and fall back to plain `pnpm` where corepack isn't present.

To get the pinned-pnpm guarantee, enable Corepack once:

```bash
corepack enable
```

Note: **Node 25+ no longer bundles Corepack** — on newer Node, install it first (`npm install -g corepack`) or just rely on the `pnpm` fallback with a locally-installed pnpm that satisfies `engines.pnpm`. See [docs/release-hardening-decisions.md](./docs/release-hardening-decisions.md) (Decision 2) for the rationale and the plan to revisit once Corepack's successor settles.

## License

This SDK is licensed under [Apache 2.0](./LICENSE).
Expand Down
Loading
Loading