fix(mobile): generate the type declarations the tsconfig already includes - #108
Merged
Conversation
…udes apps/mobile/app.json sets experiments.typedRoutes and tsconfig.json includes .expo/types/**/*.ts and expo-env.d.ts, but nothing ever wrote those files. Both type gates were inert: <Redirect href="/does-not-exist" /> type-checked at exit 0, and process.env was `any` rather than `string | undefined`. #11 added the typecheck gate that made this visible. Generation now runs as the first step of the package's typecheck script, via `expo customize tsconfig.json` — which @expo/cli's type-generation/routes.js documents as the way to run typed routes without Metro or a dev server, and which measures at ~2.2s with no bundler. EXPO_NO_TYPESCRIPT_SETUP=1 suppresses the prerequisite that would otherwise `expo install` typescript/@types/react from inside a CI check; it does not affect generation. turbo.json and the CI workflow are unchanged, so `pnpm typecheck` stays the whole gate. The files are generated rather than committed: router.d.ts is derived from the app/ route tree and a committed copy would go stale as a false pass. Generating them also exposed a live defect from the #94 scaffold. Expo Router turns every file under app/ into a route — its require.context regex excludes only +api/+html/+middleware and getRoutesCore's ignore list adds nothing for tests — so app/__tests__/index.test.tsx was a real /__tests__/index.test route and shipped @testing-library/react-native in the app bundle. Tests move to apps/mobile/__tests__/. No tsconfig change was needed; both patterns match at the project root. Two of #105's premises were wrong and are corrected in the plan file: `expo export` does not generate these files (so this is independent of #96, not sequenced behind it), and a misspelled EXPO_PUBLIC_* cannot be rejected on a bare reference — expo/types declares ProcessEnv with an open index signature, so the achievable gate is that the value is not a `string`. Closes #105 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…d tests Adversarial review of the previous commit found a regression it introduced: `expo lint` lints only src/, app/ and components/ (DEFAULT_INPUTS in @expo/cli's lint/lintAsync.js), so moving the tests out of app/ moved them out of lint's reach. Verified by injection: a no-var error in __tests__/index.test.tsx passed `expo lint` at exit 0 while the same error in app/index.tsx failed it. The lint script now runs `eslint .`, which lints everything the flat config does not ignore — no directory list to keep in sync. Passing explicit inputs to expo lint instead was rejected: unlike the defaults they skip the existence filter, so the list breaks on a missing directory and goes stale on a new one. Measured: eslint . passes clean on the current tree, fails on the injected error, and passes with the generated type files absent, so CI's lint-before-typecheck order is unaffected. ci.yml changes in a comment only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #105
apps/mobile/app.jsonsetsexperiments.typedRoutes: trueandtsconfig.jsonincludes.expo/types/**/*.tsandexpo-env.d.ts— but nothing ever wrote those files, so both type gates were inert. #11 added thetypechecktask that made this visible; this PR gives that compiler the declarations the configuration already claims it has.Decision record:
plans/ISSUE-105_Mobile_Generated_Types.mdWhat changed
turbo.jsonis unchanged;.github/workflows/ci.ymlchanges in a comment only. Generation is the first step of the package's owntypecheckscript, so #11's invariant —pnpm typecheckis the gate, passes locally ⇒ passes in CI — still holds. A CI-only step would have checked something the developer's machine never checks.expo customize tsconfig.jsonis what@expo/cli'stype-generation/routes.jsdocuments as the way to run typed routes without Metro or a dev server; measured at ~2.2s with no bundler process.EXPO_NO_TYPESCRIPT_SETUP=1suppresses the prerequisite that would otherwise runexpo installfortypescript/@types/react— i.e. network access and apackage.jsonwrite — from inside a CI check; generation does not read the variable, so the gate is unaffected.The files are generated, not committed:
router.d.tsis derived from theapp/route tree, so a committed copy goes stale as a false pass — the worst failure shape for a gate whose whole point is not being vacuous.Live defect found and fixed
Generating the route types surfaced this from the #94 scaffold:
Expo Router turns every file under
app/into a route —_ctx.*.js'srequire.contextregex excludes only+api/+html/+middleware, andgetRoutesCore.js's ignore list adds nothing for tests. Soapp/__tests__/index.test.tsxwas a real/__tests__/index.testroute and shipped@testing-library/react-nativeinside the app bundle.Tests move to
apps/mobile/__tests__/. No tsconfig change was needed (**/__tests__/**matches at the project root too);collectCoverageFromalso stops counting the test file as a source file, which it had been doing.Two of #105's premises were wrong
expo exportdoes not generate these files.startTypeScriptServiceshas exactly two callers in@expo/cli57.0.17 —DevServerManager(expo start) andcustomize/typescript.js. So typedRoutes and expo-env.d.ts are declared but never generated — both type gates are inert #105 is independent of Nothing automated exercises Metro's package-exports resolution #96, not sequenced behind it.plans/REQ-17_Task8_CI_Mobile.mdcarries a dated correction where that hypothesis was written.A misspelled
EXPO_PUBLIC_*cannot be rejected on a bare reference.expo/types/metro-require.d.tsdeclaresProcessEnvwith an open index signature ([key: string]: string | undefined), so any name reads. Measured:process.env.EXPO_PUBLIC_TYPOconst s: string = <that>expo-env.d.tsanyexpo-env.d.tsstring | undefinedTS2322The achievable gate is "a misspelled variable is not a
string". Declaring the real names lands with the code that reads them (Task 9 — Mobile core infrastructure (logger, supabase client, query client, auth store, constants, colors) #12).Verified non-vacuous
Every probe was run after
rm -rf .expo/types expo-env.d.ts, so it also proves generation happens inside the sametypecheckinvocation rather than relying on leftovers.<Redirect href="/this-route-does-not-exist" />pnpm typecheck→ exit 2,TS2322const url: string = process.env.EXPO_PUBLIC_TYPOpnpm typecheck→ exit 2,TS2322hrefunion — generation is not stalepnpm lint/pnpm typecheck/pnpm testgit statusafterwardstsconfig.json/.gitignoreonly whenextendsor the twoincludeentries are missing, and ours has all threeProbes for both gates are now permanent in
apps/mobile/README.md, in the exact form they were run.Second commit — found in adversarial review
Reviewing the first commit for holes found a regression it introduced: moving the tests out of
app/moved them out ofexpo lint's reach. The wrapper lints onlysrc/app/components(DEFAULT_INPUTSin@expo/cli'slint/lintAsync.js). Verified by injection — ano-varerror in__tests__/index.test.tsxpassedexpo lintat exit 0 while the same error inapp/index.tsxfailed it.Fix:
"lint": "eslint ."— lints everything the flat config does not ignore, so there is no directory list to keep in sync. Explicit inputs toexpo lintwere rejected: unlike the defaults they skip the existence filter, so the list breaks on a missing directory and goes stale on a new one. Measured: clean pass on the current tree, exit 1 on the injected error, exit 0 with the generated files absent.