Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
43e8b0d
chore(yarn): upgrade to yarn 4 binary and migrate lockfiles
oliverlaz May 13, 2026
26d5570
feat(repo): declare yarn workspaces and convert link protocols
oliverlaz May 13, 2026
f0236f6
fix(examples): move sync-native off preinstall
oliverlaz May 13, 2026
d9cba36
chore(scripts): migrate routine scripts off lerna to yarn workspace
oliverlaz May 13, 2026
edfea1d
feat(release): replace lerna with yarn workspaces foreach
oliverlaz May 13, 2026
ea09e94
chore(husky): wire husky install + shared-native sync via core postin…
oliverlaz May 13, 2026
0b668ee
chore(yarn): enable hardened mode and supply-chain settings
oliverlaz May 13, 2026
b374931
chore(yarn): disable install scripts and allowlist required builds
oliverlaz May 13, 2026
aefbd8e
ci: adopt yarn 4 caching and consolidated installs
oliverlaz May 13, 2026
fea39e6
docs: document yarn 4 + workspaces setup
oliverlaz May 13, 2026
bf2b97e
chore(deps): bump husky to ^9.1.7 and drop migration plan file
oliverlaz May 13, 2026
cf9948e
ci: fix deprecated ::set-output and tighten .yarn gitignore
oliverlaz May 13, 2026
8fb4cc6
chore(deps): unpin prettier and react-native-keyboard-controller
oliverlaz May 13, 2026
2949810
chore(deps): bump semantic-release to 25.0.3
oliverlaz May 13, 2026
f4aded6
chore(yarn): drop approvedGitRepositories wildcard
oliverlaz May 13, 2026
c2153f0
chore(lint): consolidate eslint and prettier into the root workspace
oliverlaz May 13, 2026
20da02b
chore: tier-1 monorepo cleanups
oliverlaz May 13, 2026
7980de5
chore: tier-2 monorepo cleanups + lint loose ends
oliverlaz May 13, 2026
4b5a71f
chore(lint): drop @react-native/eslint-config, bump @shopify/flash-list
oliverlaz May 13, 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
117 changes: 117 additions & 0 deletions .claude/plans/2026-05-13-consolidate-eslint-prettier-into-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# Consolidate ESLint + Prettier into the root workspace

## Context

Today every workspace in the monorepo carries its own lint tooling:

- `package/eslint.config.mjs` (full SDK config, uses the legacy `@react-native-community/eslint-plugin@1.3.0` and `@react-native-community/eslint-config@3.2.0`, integrates Prettier via `eslint-plugin-prettier`, lints Markdown).
- `examples/SampleApp/eslint.config.mjs` (lightweight, uses modern `@react-native/eslint-config@0.81.6` β€” only `jsx-quotes` + `no-inline-styles` customizations).
- `examples/TypeScriptMessaging/eslint.config.mjs` (same lightweight shape, modern `@react-native/eslint-config@0.80.2`).
- `examples/ExpoMessaging`, `package/native-package`, `package/expo-package` β€” no ESLint config, no scripts.
- Every workspace except the three smallest declares its own copy of `eslint`, `typescript-eslint`, `prettier`, and the React/RN plugins. There is **version drift** (`prettier ^3.5.1` at root vs `^3.5.3` everywhere else; two different RN ESLint config families).

Prettier is already nearly centralized β€” `.prettierrc` and `.prettierignore` live at the repo root and are referenced by core via `../.prettierrc`. Only the `prettier` binary itself is duplicated.

CI (`.github/workflows/check-pr.yml:33`, `.github/workflows/release.yml:48`) and the pre-commit hook (`dotgit/hooks/pre-commit-format.sh:5-6`) both invoke `yarn lint` at the repo root, which today proxies to `yarn workspace stream-chat-react-native-core lint`.

**Goal:** one shared ESLint config and one Prettier setup, both owned by the root workspace. Sub-workspaces declare zero eslint/prettier dependencies, ship no config files, and the existing `yarn lint` / `yarn lint-fix` / pre-commit hook / CI entry points keep working unchanged.

**User decisions captured in this plan:**

1. Standardize on the modern **`@react-native/eslint-config`** (single version) and drop `@react-native-community/eslint-*`.
2. **Same strictness everywhere** β€” one shared rule set applied to `package/**` and `examples/**` alike. Expect a one-time wave of fixes/disables in the example apps.
3. **Keep the current Prettier integration**: `eslint-plugin-prettier` + `eslint-config-prettier` inside the ESLint config, and a separate `prettier --list-different` step in the lint script.

## Approach

- Add **one** `eslint.config.mjs` at the repo root, derived from `package/eslint.config.mjs` (full feature set: TS, React, React-Native, import order, Prettier, Jest, Markdown) but with **`@react-native-community/eslint-*` replaced by `@react-native/eslint-config` + `@react-native/eslint-plugin`** and the `globals` parsing logic adapted (the modern config uses `true` instead of `'readonly'` β€” see `examples/SampleApp/eslint.config.mjs:17-22` for the existing pattern).
- The shared config applies to all source files; **file-glob overrides** keep small workspace-specific behaviors (Jest test files, example apps' lower bar where unavoidable β€” e.g. ignoring generated files under `examples/*/ios/build/`, Metro/Babel config files).
- Move every eslint/prettier-related devDependency to **root `devDependencies`**. Delete them from sub-workspace `package.json`s. Yarn 4 with `nmHoistingLimits: workspaces` puts root devDeps in `<repo>/node_modules/`, so Node resolution walks up and finds them from any sub-workspace.
- Delete `package/eslint.config.mjs`, `examples/SampleApp/eslint.config.mjs`, `examples/TypeScriptMessaging/eslint.config.mjs`. ESLint flat-config auto-discovery walks up from the cwd until it finds an `eslint.config.mjs`, so a single config at the root covers every workspace.
- Redefine the **root `lint` / `lint-fix` / `eslint` scripts** to do the work in place (instead of proxying to a workspace). New shape:
- `lint`: `prettier --list-different . && eslint . --max-warnings 0 && yarn workspace stream-chat-react-native-core validate-translations`
- `lint-fix`: `prettier --write . && eslint . --fix --max-warnings 0`
- `eslint`: `eslint .`
- `prettier`: `prettier --list-different .`
- `prettier-fix`: `prettier --write .`
- The core package keeps its **`validate-translations`** script (it depends on translation files that only exist under `package/src/i18n/`); the new root `lint` script calls into it via `yarn workspace`.
- Sub-workspace `lint`/`eslint`/`lint-fix`/`prettier*` scripts are **deleted** (with one exception: `validate-translations` stays in core).
- The pre-commit hook and the CI workflows already call `yarn lint` from the repo root β€” no changes needed there.
- `.prettierignore` and `.prettierrc` stay where they are at the root.
- Extend `.prettierignore` to also exclude example app build artifacts that aren't currently listed but will start being scanned once Prettier runs from the root (`examples/*/ios/Pods/`, `examples/*/android/build/`, `examples/*/.expo/`, `examples/SampleApp/patches/` if relevant, etc.). Verify by running `prettier --list-different .` after the move and adding any noisy paths to the ignore file rather than fixing them.

## Files to change

**Add**

- `eslint.config.mjs` β€” new root flat config (copy of `package/eslint.config.mjs` with modern `@react-native/*` imports + adapted globals parsing + broader `ignores` covering all workspaces' build outputs + example-app overrides if needed).

**Modify**

- `package.json` (root): add eslint/prettier-related devDeps; rewrite `eslint` / `lint` / `lint-fix` scripts; add `prettier` / `prettier-fix` scripts; bump `prettier` to `^3.5.3` to match what core/examples already pin.
- `.prettierignore`: extend with any example-app paths that surface noise once Prettier scans the whole repo.
- `package/package.json`: remove every eslint/prettier-related entry from `devDependencies` (`eslint`, `typescript-eslint`, `eslint-config-prettier`, `eslint-plugin-prettier`, `eslint-plugin-eslint-comments`, `eslint-plugin-import`, `eslint-plugin-jest`, `eslint-plugin-markdown`, `eslint-plugin-react`, `eslint-plugin-react-hooks`, `eslint-plugin-react-native`, `@react-native-community/eslint-config`, `@react-native-community/eslint-plugin`, `prettier`); remove `eslint` / `lint` / `lint-fix` / `prettier` / `prettier-fix` scripts; **keep `validate-translations`**.
- `examples/SampleApp/package.json`: remove eslint/prettier devDeps; remove `lint` / `eslint` / `lint-fix` scripts.
- `examples/TypeScriptMessaging/package.json`: remove eslint/prettier devDeps; remove `lint` script.

**Delete**

- `package/eslint.config.mjs`
- `examples/SampleApp/eslint.config.mjs`
- `examples/TypeScriptMessaging/eslint.config.mjs`

**Unchanged (no edits needed, but verify nothing breaks)**

- `.prettierrc` (already root-owned)
- `.husky/pre-commit` and `dotgit/hooks/pre-commit-format.sh` (call `yarn lint`, which still resolves to the redefined root script)
- `.github/workflows/check-pr.yml`, `.github/workflows/release.yml` (call `yarn lint`)
- `.vscode/settings.json` (only sets `formatOnSave`, picks up root `.prettierrc` automatically)
- `package/.editorconfig`

## Step-by-step migration

1. **Snapshot baseline** β€” run `yarn lint` on `develop` and save the pass/fail output, so any new errors after the migration are clearly attributable to the new ruleset.
2. **Add root `eslint.config.mjs`** derived from core's. Concretely:
- Replace `@react-native-community/eslint-config` β†’ `@react-native/eslint-config`, `@react-native-community/eslint-plugin` β†’ `@react-native/eslint-plugin`, and rename the corresponding `plugins` key from `'@react-native-community'` to `'@react-native'`.
- Adapt the globals reducer (the modern config stores `true`/`false`, not `'readonly'`) β€” pattern shown in `examples/SampleApp/eslint.config.mjs:17-22`.
- Broaden the top-level `ignores` to cover every workspace: `node_modules/`, `**/build/`, `**/dist/`, `**/lib/`, `**/.expo/`, `**/vendor/`, `**/ios/build/`, `**/ios/Pods/`, `**/android/build/`, `**/android/app/build/`, `package/src/components/docs/`, plus any Metro-generated dirs in examples.
- Keep the Jest overlay for `**/__tests__/**`, `**/*.test.*`, and `src/mock-builders/**` (the glob already covers all workspaces; verify with a dry run).
- If example apps trip new rules that aren't worth fixing immediately, add a third overlay `{ files: ['examples/**/*.{js,ts,tsx,jsx}'], rules: { ... } }` with targeted relaxations. Prefer fixing real issues over piling up disables.
3. **Update root `package.json`**:
- Add devDeps (versions match what's installed today in core/examples to minimize lockfile churn): `eslint ^9.28.0`, `typescript-eslint ^8.34.0`, `eslint-config-prettier ^10.1.5`, `eslint-plugin-prettier ^5.4.1`, `eslint-plugin-eslint-comments ^3.2.0`, `eslint-plugin-import ^2.31.0`, `eslint-plugin-jest ^28.13.3`, `eslint-plugin-markdown ^5.1.0`, `eslint-plugin-react ^7.37.5`, `eslint-plugin-react-hooks ^5.2.0`, `eslint-plugin-react-native ^5.0.0`, `@react-native/eslint-config ^0.81.6`, `@react-native/eslint-plugin ^0.81.6`. Bump root `prettier` from `^3.5.1` to `^3.5.3`.
- Rewrite scripts as described in the Approach section.
4. **Strip sub-workspace `package.json`s** of eslint/prettier devDeps and lint scripts. Delete their `eslint.config.mjs` files.
5. **Run `yarn install`** to regenerate `yarn.lock`. Confirm only intended deps moved/were removed.
6. **Run `yarn lint`** from the repo root. Triage:
- True regressions β†’ fix in code.
- Stylistic differences from the modern `@react-native/eslint-config` vs the legacy community plugin β†’ reconcile by adjusting the root config rules (favour preserving today's core behavior where it conflicts with the modern defaults).
- Example-app noise from now-stricter linting β†’ fix or relax via the `examples/**` overlay.
7. **Run `yarn lint-fix`** and re-run `yarn lint`. Expect a clean pass.
8. **Run the pre-commit hook locally** (`./dotgit/hooks/pre-commit-format.sh` after staging a small no-op change) to confirm it still works.
9. **Spot-check editor integration**: open a file in `examples/SampleApp/` in VSCode, save, and confirm Prettier formats it and ESLint diagnostics show up.
10. **Push & verify CI** β€” `check-pr.yml`'s `yarn lint` step is the canonical signal.

## Verification

- `yarn install --immutable` succeeds after the lockfile regeneration commit is in place.
- `yarn lint` from the repo root exits 0 and reports zero warnings (`--max-warnings 0`).
- `yarn lint` from any sub-workspace directory (`cd examples/SampleApp && yarn lint`) either runs the root script (if we keep a thin proxy) or fails with a clear "no script" message β€” confirm the chosen behavior is documented.
- `yarn lint-fix` rewrites only intended files; `git status` after a clean checkout + `yarn lint-fix` shows no diff.
- `node -e "require('eslint/package.json').version"` from `examples/SampleApp/` returns the root-installed version (proves hoisting works).
- Pre-commit hook: introduce a deliberate formatting violation in a staged file, attempt `git commit`, confirm it is rejected with the existing message.
- CI: `check-pr.yml` and `release.yml` both green on a draft PR.
- Grep sanity check: `grep -R "eslint-plugin\|@react-native-community/eslint\|@react-native/eslint" package examples` returns no matches in any `package.json` other than the root.

## Risks & mitigations

- **Rule-set drift between legacy and modern RN configs.** The modern `@react-native/eslint-config` ships a different default rule set than `@react-native-community/eslint-config@3.2.0`. Mitigation: derive the root config from the current core config and override modern defaults to match today's behavior wherever they conflict; treat any net-new errors as either a real bug or a targeted disable.
- **Yarn hoisting edge cases.** With `nmHoistingLimits: workspaces`, root devDeps land in `<repo>/node_modules/` and resolve via Node walking up. If a sub-workspace ships its own copy of a peer-dependency that conflicts, ESLint plugin resolution can pick the wrong one. Mitigation: after `yarn install`, run `yarn why eslint` and `yarn why @react-native/eslint-config` and confirm a single instance is hoisted to the root.
- **Different RN versions in examples.** `examples/SampleApp` is on RN 0.81.x; `examples/TypeScriptMessaging` is on RN 0.80.x. We're pinning one `@react-native/eslint-config` version. Lint rules are largely version-agnostic; the worst case is a couple of cosmetic differences that get resolved by `yarn lint-fix`.
- **Markdown linting.** The existing core config keeps `eslint-plugin-markdown` in deps but the active config doesn't actually wire it up as a plugin (the file extension is in the `eslint` glob, not the flat config plugins). Verify whether dropping `eslint-plugin-markdown` entirely is safe; if so, omit it from the root devDeps. (Decision deferred to execution β€” check by removing the dep, running lint, and seeing if anything regresses.)
- **`enableHardenedMode: true` in `.yarnrc.yml`** means dependency additions are scrutinized. None of the deps we're moving are new to the repo (they all already exist in sub-workspaces), so the audit surface doesn't grow. Lockfile regen should be uneventful.

## Out of scope

- Modernizing the rule set (e.g. enabling new TypeScript strictness rules, tightening `react-hooks/exhaustive-deps` from `warn` to `error`). The migration aims for behavioral parity; rule changes are a follow-up.
- Switching to a separate ESLint config package (e.g. `tooling/eslint-config-stream-chat-rn`). The root-config approach is simpler and matches the "root owns the config" intent. If we ever publish other RN SDKs from this repo, that can be revisited.
- Touching test runners, TypeScript configs, or Husky setup.
File renamed without changes.
20 changes: 3 additions & 17 deletions .github/actions/install-and-build-sdk/action.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,8 @@
name: 'Install and Build SDK'
description: 'Runs yarn install for all the packages and sample and fails if yarn lock has a change that is not committed'
description: 'Runs yarn install at the workspace root and fails if yarn.lock has uncommitted changes'
runs:
using: 'composite'
steps:
- name: Install Root repo dependencies
run: yarn --frozen-lockfile
shell: bash
- name: Install & Build the Core Package
run: |
cd package/
yarn --frozen-lockfile
shell: bash
- name: Install & Build the Native Package
run: |
cd package/native-package/
yarn
shell: bash
- name: Install & Build the Sample App
working-directory: examples/SampleApp
run: yarn
- name: Install workspace dependencies
run: yarn install --immutable
shell: bash
31 changes: 11 additions & 20 deletions .github/workflows/changelog-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,6 @@ jobs:
matrix:
node-version: [ 24.x ]
steps:
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- uses: actions/checkout@v6
with:
ref: develop
Expand All @@ -31,15 +25,12 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Installation
run: |
yarn --frozen-lockfile
cd package/
yarn --frozen-lockfile
cd native-package/
yarn
cd ../../examples/SampleApp/
yarn
run: yarn install --immutable

- name: Generate Changelog
id: generate_changelog
Expand All @@ -54,13 +45,13 @@ jobs:

echo "Changelog file ready! Setting up outputs"
CHANGELOG_PREVIEW=$(cat NEXT_RELEASE_CHANGELOG.md)

CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW//'%'/'%25'}"
CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW_ESCAPED//$'\n'/'%0A'}"
CHANGELOG_PREVIEW_ESCAPED="${CHANGELOG_PREVIEW_ESCAPED//$'\r'/'%0D'}"

echo "::set-output name=exists::true"
echo "::set-output name=preview::$CHANGELOG_PREVIEW_ESCAPED"
echo "exists=true" >> "$GITHUB_OUTPUT"
{
echo "preview<<CHANGELOG_PREVIEW_EOF"
echo "$CHANGELOG_PREVIEW"
echo "CHANGELOG_PREVIEW_EOF"
} >> "$GITHUB_OUTPUT"
fi
- uses: marocchino/sticky-pull-request-comment@v3
if: steps.generate_changelog.outputs.exists == 'true'
Expand Down
6 changes: 4 additions & 2 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ jobs:
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- name: Install && Build - SDK and Sample App
uses: ./.github/actions/install-and-build-sdk
- name: Lint
run: yarn lerna-workspaces run lint
run: yarn lint
- name: Typecheck tests
run: cd package && yarn test:typecheck
run: yarn workspace stream-chat-react-native-core test:typecheck
- name: Test
run: yarn test:coverage
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
registry-url: 'https://registry.npmjs.org'
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential

Expand All @@ -43,7 +45,7 @@ jobs:
uses: ./.github/actions/install-and-build-sdk

- name: Lint
run: yarn lerna-workspaces run lint
run: yarn lint

- name: Test
if: github.ref == 'refs/heads/develop'
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/sample-distribution.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ jobs:
uses: webfactory/ssh-agent@v0.10.0
with:
ssh-private-key: ${{ secrets.BOT_SSH_PRIVATE_KEY }}
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- uses: actions/checkout@v6
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '26.2' # Update as needed
Expand Down Expand Up @@ -64,11 +66,13 @@ jobs:
matrix:
node-version: [24.x]
steps:
- uses: actions/checkout@v6
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
- uses: actions/checkout@v6
cache: 'yarn'
cache-dependency-path: 'yarn.lock'
- name: Install Linux build tools
run: sudo apt-get update && sudo apt-get install -y build-essential
- uses: actions/setup-java@v5
Expand Down
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@
!.yarn/sdks
!.yarn/versions

# In case someone runs `yarn install` inside a workspace child, Yarn 4 will
# emit a transient .yarn/install-state.gz there; the root-anchored pattern
# above doesn't catch nested copies.
**/.yarn/install-state.gz
**/.yarn/cache

node_modules
NEXT_RELEASE_CHANGELOG.md
artifacts
Expand Down
4 changes: 0 additions & 4 deletions .husky/commit-msg
Original file line number Diff line number Diff line change
@@ -1,5 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx commitlint --edit $1
npx commitlint --edit $1
3 changes: 0 additions & 3 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,4 +1 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

dotgit/hooks/pre-commit-format.sh && dotgit/hooks/pre-commit-reject-binaries.py
Loading
Loading