|
| 1 | +# CI Troubleshooting Guide |
| 2 | + |
| 3 | +A reference for the checks that run against every pull request — what they |
| 4 | +check, the exact command to reproduce a failure locally, and fixes for the |
| 5 | +errors contributors hit most often. |
| 6 | + |
| 7 | +This guide is about **fixing red checks**. For initial project setup, see the |
| 8 | +[Mobile Onboarding Checklist](mobile-onboarding-checklist.md). For what |
| 9 | +evaluators look for on GrantFox contract issues, see the |
| 10 | +[Evaluation Readiness Checklist](evaluation-readiness-checklist.md). |
| 11 | + |
| 12 | +> ⚠️ **Failing CI can block PR approval and payment evaluation.** Reviewers |
| 13 | +> and the GrantFox evaluation process expect all checks to pass before a PR |
| 14 | +> is merged. Don't skip, mute, or work around a failing check — fix the |
| 15 | +> underlying problem, or ask a maintainer if you believe the check itself is |
| 16 | +> wrong. |
| 17 | +
|
| 18 | +## Run These Locally Before You Push |
| 19 | + |
| 20 | +Reproduce what CI checks in four commands: |
| 21 | + |
| 22 | +```bash |
| 23 | +npm install --legacy-peer-deps # dependencies |
| 24 | +npm run typecheck # TypeScript |
| 25 | +npm run lint # lint / formatting |
| 26 | +npm test # unit and integration tests |
| 27 | +``` |
| 28 | + |
| 29 | +If your change touches `src/types/pocketpay-sdk.d.ts` or `src/sdk-stub/`, |
| 30 | +also run `npm run api:check` (see |
| 31 | +[SDK API Compatibility](sdk-api-compatibility.md)). |
| 32 | + |
| 33 | +All four commands must exit cleanly before you open a PR. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Dependency Errors |
| 38 | + |
| 39 | +### `npm install` fails with peer dependency conflicts |
| 40 | + |
| 41 | +``` |
| 42 | +npm error ERESOLVE unable to resolve dependency tree |
| 43 | +``` |
| 44 | + |
| 45 | +This project has known React Native peer dependency conflicts. Always install |
| 46 | +with: |
| 47 | + |
| 48 | +```bash |
| 49 | +npm install --legacy-peer-deps |
| 50 | +``` |
| 51 | + |
| 52 | +A plain `npm install` (without the flag) is expected to fail — that's not a |
| 53 | +broken project, it's a missing flag. |
| 54 | + |
| 55 | +### Multiple lock files |
| 56 | + |
| 57 | +Run `npx expo-doctor` and you may see: |
| 58 | + |
| 59 | +``` |
| 60 | +✖ Check for lock file |
| 61 | +Multiple lock files detected (pnpm-lock.yaml, package-lock.json, bun.lock). |
| 62 | +This may result in unexpected behavior in CI environments, such as EAS |
| 63 | +Build, which infer the package manager from the lock file. |
| 64 | +``` |
| 65 | + |
| 66 | +Expo's CLI infers the package manager from whichever lock file it finds. If a |
| 67 | +stray `bun.lock` or `pnpm-lock.yaml` ends up in your working tree (for |
| 68 | +example, from running `bunx` or `pnpm install` once out of habit), Expo |
| 69 | +tooling can silently switch to that package manager instead of npm. If that |
| 70 | +package manager isn't installed, commands fail with something like: |
| 71 | + |
| 72 | +``` |
| 73 | +✖ Failed to install eslint@^9.0.0, eslint-config-expo@~10.0.0 with error: spawn bun ENOENT |
| 74 | +``` |
| 75 | + |
| 76 | +This project uses **npm** — `package-lock.json` is the only lock file that |
| 77 | +should exist. If you see extra lock files in `git status`, delete the ones |
| 78 | +you didn't intentionally create and reinstall with npm. |
| 79 | + |
| 80 | +### `postinstall` seems stuck or the app fails to build with an SDK error |
| 81 | + |
| 82 | +The `postinstall` script builds the PocketPay SDK from a pinned source commit |
| 83 | +(it isn't published to npm), which takes longer than a typical install. Let |
| 84 | +it run to completion. If a build genuinely fails partway through: |
| 85 | + |
| 86 | +```bash |
| 87 | +rm -rf node_modules |
| 88 | +npm install --legacy-peer-deps |
| 89 | +``` |
| 90 | + |
| 91 | +--- |
| 92 | + |
| 93 | +## TypeScript Errors (`npm run typecheck`) |
| 94 | + |
| 95 | +`npm run typecheck` runs `tsc --noEmit` — no output is emitted, only type and |
| 96 | +syntax errors are reported. Read the **first** error in the output first; |
| 97 | +syntax errors (unbalanced braces, unclosed JSX tags) frequently cascade into |
| 98 | +dozens of unrelated-looking errors further down the file. Fixing the first |
| 99 | +one often clears most of the rest. Common shapes you'll see: |
| 100 | + |
| 101 | +- `error TS2657: JSX expressions must have one parent element.` — a |
| 102 | + component returns multiple sibling elements without a single wrapping |
| 103 | + element or `<>...</>` fragment. |
| 104 | +- `error TS1005: '}' expected.` / `error TS17015: Expected corresponding |
| 105 | + closing tag for JSX fragment.` — a missing or extra closing brace/tag |
| 106 | + earlier in the file; scroll up from the reported line. |
| 107 | + |
| 108 | +The TypeScript compiler reports the *symptom's* location, not always the |
| 109 | +*cause's* location — if an error looks nonsensical at the reported line, |
| 110 | +check the nearest unclosed brace or tag above it. |
| 111 | + |
| 112 | +--- |
| 113 | + |
| 114 | +## Lint / Formatting (`npm run lint`) |
| 115 | + |
| 116 | +This project doesn't have a separate `prettier`/`eslint` setup — linting goes |
| 117 | +through Expo's built-in wrapper, which uses `eslint-config-expo` under the |
| 118 | +hood: |
| 119 | + |
| 120 | +```bash |
| 121 | +npm run lint |
| 122 | +``` |
| 123 | + |
| 124 | +- On a machine without ESLint configured yet, `expo lint` offers to install |
| 125 | + `eslint` and `eslint-config-expo` automatically. Let it — it only touches |
| 126 | + `devDependencies`. |
| 127 | +- Auto-fix what can be auto-fixed (the `--` is required so npm passes `--fix` |
| 128 | + through to `expo lint` instead of swallowing it as an npm flag): |
| 129 | + ```bash |
| 130 | + npm run lint -- --fix |
| 131 | + ``` |
| 132 | +- If the auto-install step fails with `spawn bun ENOENT` (or a similar |
| 133 | + "package manager not found" error), see **Multiple lock files** above — |
| 134 | + Expo picked the wrong package manager because of a stray lock file. |
| 135 | + |
| 136 | +--- |
| 137 | + |
| 138 | +## Test Failures (`npm test`) |
| 139 | + |
| 140 | +`npm test` runs `jest --watchAll=false` once and exits. A few failure |
| 141 | +patterns show up repeatedly: |
| 142 | + |
| 143 | +### `SyntaxError: Unexpected token 'export'` |
| 144 | + |
| 145 | +``` |
| 146 | +node_modules/some-package/dist/esm/some-package.mjs:8 |
| 147 | +export { default as Foo } from './icons/foo.mjs'; |
| 148 | +^^^^^^ |
| 149 | +SyntaxError: Unexpected token 'export' |
| 150 | +``` |
| 151 | + |
| 152 | +Jest runs tests through Node, which doesn't understand ES module `export` |
| 153 | +syntax out of the box. This happens when a `node_modules` package ships an |
| 154 | +ESM build (`dist/esm/*.mjs`) and Jest's `transformIgnorePatterns` doesn't |
| 155 | +include it, so Jest tries to run the raw `.mjs` file instead of transforming |
| 156 | +it. It surfaces in any test that imports a component which imports the |
| 157 | +untransformed package (for example, an icon library like |
| 158 | +`lucide-react-native`). |
| 159 | + |
| 160 | +**Fix:** add the offending package to the `transformIgnorePatterns` entry in |
| 161 | +the `"jest"` block of `package.json` so Jest transforms it instead of |
| 162 | +skipping it. If you're not sure which package is at fault, the file path in |
| 163 | +the error (`node_modules/<package>/...`) tells you. |
| 164 | + |
| 165 | +### `Your test suite must contain at least one test` |
| 166 | + |
| 167 | +``` |
| 168 | +FAIL tests/SomeScreen.test.tsx |
| 169 | +● Test suite failed to run |
| 170 | + Your test suite must contain at least one test. |
| 171 | +``` |
| 172 | + |
| 173 | +A test file exists but has no `it(...)`/`test(...)` blocks — usually a |
| 174 | +placeholder file created before the tests were written, or every test inside |
| 175 | +was commented out. Either add at least one real test or delete the file; an |
| 176 | +empty test file left in the repo will fail CI every time. |
| 177 | + |
| 178 | +### `Exceeded timeout of 5000 ms for a test` |
| 179 | + |
| 180 | +Jest's default per-test timeout is 5 seconds. This usually means a promise |
| 181 | +never resolves — a mock that doesn't call back, or an `await` on something |
| 182 | +that's waiting on a real timer/network call that doesn't exist in the test |
| 183 | +environment. Check that every async dependency (Stellar SDK calls, |
| 184 | +`SecureStore`, timers) is mocked — see the patterns in `__mocks__/` and |
| 185 | +`src/services/__mocks__/`. Only raise the test's timeout |
| 186 | +(`it('...', async () => { ... }, 10000)`) once you've confirmed the test is |
| 187 | +legitimately slow rather than actually hung. |
| 188 | + |
| 189 | +### Missing mocks for native/SDK modules |
| 190 | + |
| 191 | +If a test throws while importing `expo-secure-store`, `expo-router`, or the |
| 192 | +Stellar SDK, check whether an existing mock under `__mocks__/` or |
| 193 | +`src/services/__mocks__/` already covers it before writing a new one — most |
| 194 | +native and SDK dependencies already have a project-level mock to reuse. |
| 195 | + |
| 196 | +--- |
| 197 | + |
| 198 | +## Expo-Specific Setup Issues (`npx expo-doctor`) |
| 199 | + |
| 200 | +`npx expo-doctor` runs a broader set of Expo/EAS-oriented health checks |
| 201 | +beyond what `tsc` and `jest` cover — config schema validity, native module |
| 202 | +compatibility, and package manager consistency: |
| 203 | + |
| 204 | +```bash |
| 205 | +npx expo-doctor |
| 206 | +``` |
| 207 | + |
| 208 | +- **Multiple lock files** — see above. |
| 209 | +- **`Check Expo config (app.json/ app.config.js) schema` fails with `fetch |
| 210 | + failed`** — this check calls Expo's remote API to validate `app.json` |
| 211 | + against the current schema. In a sandboxed or offline environment without |
| 212 | + outbound network access, this check cannot run and will fail regardless of |
| 213 | + whether your config is valid. If every other check passes and you have no |
| 214 | + network access, this is expected — re-run it somewhere with internet |
| 215 | + access before relying on its result. |
| 216 | +- **Blank screen or a stale Metro bundler error** — clear the Metro cache: |
| 217 | + ```bash |
| 218 | + npx expo start --clear |
| 219 | + ``` |
| 220 | + |
| 221 | +--- |
| 222 | + |
| 223 | +## Clean Install ("Nuke It From Orbit") |
| 224 | + |
| 225 | +When in doubt, or after switching branches with very different dependencies: |
| 226 | + |
| 227 | +```bash |
| 228 | +rm -rf node_modules |
| 229 | +rm -f pnpm-lock.yaml bun.lock # keep only package-lock.json |
| 230 | +npm install --legacy-peer-deps |
| 231 | +npm run typecheck |
| 232 | +npm run lint |
| 233 | +npm test |
| 234 | +``` |
| 235 | + |
| 236 | +If all four still fail identically after a clean install, the problem is |
| 237 | +almost certainly in your changes rather than your environment. |
| 238 | + |
| 239 | +--- |
| 240 | + |
| 241 | +## What Reviewers and Evaluators Expect |
| 242 | + |
| 243 | +- All required checks pass on the PR — don't open a PR with known-failing |
| 244 | + checks and a promise to "fix it after." |
| 245 | +- If a check fails for a reason unrelated to your change (a pre-existing |
| 246 | + issue on `main`), say so explicitly in the PR description rather than |
| 247 | + silently leaving it red. |
| 248 | +- For GrantFox contract issues specifically, a green CI run is necessary but |
| 249 | + not sufficient — see the |
| 250 | + [Evaluation Readiness Checklist](evaluation-readiness-checklist.md) for the |
| 251 | + full bar before payment evaluation. |
0 commit comments