chore: migrate to the magic tooling stack (pnpm + oxlint + oxfmt) - #9
Conversation
Replaces yarn with pnpm (workspace, `packageManager` pin, single lockfile) and drops eslint, @react-native-community/eslint-config, prettier and its two eslint bridges for oxlint 1.75.0, oxfmt 0.60.0 and the shared magic-oxlint-config / magic-oxfmt-config / magic-tsconfig packages. The `example/` app becomes a workspace package, which changes two things it depended on: - `main` pointed at `node_modules/expo/AppEntry.js`, a path that no longer exists once pnpm hoists to the workspace root. Replaced with the documented `registerRootComponent` entry in `example/index.ts`. - `metro.config.js` used a blockList/extraNodeModules pair to stop a second copy of every peer dependency being loaded out of the root. One install means one copy, so it is now the standard Expo monorepo config (`watchFolders` + `nodeModulesPaths`). `.yarnrc` and `scripts/bootstrap.js` go with yarn; `pnpm install` already does what `yarn bootstrap` did. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
`oxlint.config.mts` extends the `react-native` preset and re-declares its `ignorePatterns` — oxlint does not inherit those through `extends` — plus `lib/`, the bob build output. `tsconfig.json` extends `magic-tsconfig/base.json`; `tsconfig.build.json` pins `rootDir` to the repo root so bob keeps emitting declarations at `lib/typescript/src/index.d.ts`, which is where package.json `types` points. `.nvmrc` records the Node version CI already used, so the reusable workflow can read it. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
`unicorn/filename-case` is on at kebabCase in the shared preset. Applied with `magic-kebab --write`: 5 renames, 6 import specifiers rewritten, 0 skipped, 0 conflicts, 0 needing review. `example/App.tsx` stays as it is — the react-native preset exempts it and the codemod skips it, because Expo's `AppEntry.js` imports `../../App` from inside node_modules where nothing can rewrite it. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
Eighteen diagnostics, five of them the filename renames already committed. The rest: - `typescript/consistent-type-imports` (5) — autofixed to top-level `import type`. - `react-native/no-color-literals` (5) — the library's three colours move to `src/colors.ts`, the example app's two to a const at the top of `App.tsx`. The icon `fill="white"` props use the same constant, since it is the same decision. - `react/function-component-definition` (1) — `example/App.tsx` becomes an arrow function with a separate default export. - `jest/valid-title` (2) — "renders alerts correctly" says nothing "renders an alert toast" doesn't. Snapshot keys renamed in place rather than regenerated, so `jest --ci` proves the rendered output is unchanged. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
First run of the shared formatter. Two things account for most of the diff: oxfmt's house style is Prettier's defaults (double quotes, where the old `prettier` block in package.json set `singleQuote: true`), and oxfmt sorts `package.json` keys — the values are untouched, only the order and the `keywords` array changed. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
The job did checkout / setup-node with a yarn cache / install / lint / typescript / test / prepare by hand. All of that now comes from `GSTJ/magic/.github/workflows/ci.yml`, which adds a format check and reads the Node version from `.nvmrc` and pnpm from `packageManager`. Build and test stay opted in, so the workflow still fails on a broken build or a broken test. Turbo caching is off — there is no turbo here. `renovate.json` extends `github>GSTJ/magic`, which is where the grouping rules that keep oxlint and oxfmt pinned together live. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
magic-oxlint-config, magic-oxfmt-config, magic-tsconfig and magic-codemods all move from 1.0.0 to 1.1.0. The minimumReleaseAgeExclude entries move with them. pnpm 11 verifies the lockfile against the 24h quarantine before it resolves anything, so the 1.0.0 entries had to stay in place for the install that removed them, then come out afterwards. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
|
Updated to the patched packages: Workarounds removedThe
There was nothing else to strip: no local rule overrides in this repo, no New violations
Notespnpm 11 verifies the committed lockfile against the release quarantine before it resolves anything, so
|
The `as OxlintConfig` cast is gone. 1.1.0 types `plugins` as oxlint's own
15-member union and `rules` as its rule-entry shape, so the preset object is
assignable to `extends` without help.
`extends` still drops ignorePatterns on oxlint 1.75.0. `--print-config` on
`defineConfig({ extends: [reactNative] })` reports `"ignorePatterns": []`, so
the re-declaration could not just be deleted. The config now goes through
`extendConfig` instead, which flattens the preset and the local object into
one config and unions the arrays. That leaves `**/lib/**`, bob's output
directory, as the only thing this repo declares.
magic-tsconfig 1.1.0 drops `incremental`, so nothing writes a .tsbuildinfo
any more and the .gitignore entry goes with it.
Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
typescript/consistent-type-definitions ships with the "type" option in magic-oxlint-config 1.1.0, so every `interface` is now an error. `oxlint --fix` handled all three; oxfmt added the trailing semicolons. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
02bab18 to
94d877a
Compare
magic-oxlint-config, magic-oxfmt-config and magic-tsconfig go to 1.2.0. magic-codemods stays on 1.1.0 and magic-oxlint-plugin is not a dependency here. Nothing in the release changes what this repo runs. The oxlint fix mirrors env/globals into a `files: ["**"]` override so they survive oxlint's `extends`; this repo goes through `extendConfig`, which flattens instead, so it already had both. `incremental` is back in `nextjs.json` only, and this repo extends `base.json` and `expo.json`, so no .tsbuildinfo appears and the .gitignore entry dropped in the 1.1.0 round stays dropped. oxfmt's new `withoutIgnorePatterns()` is for repos that hand-write CHANGELOG.md; this one generates it from release-it and wants the default ignore. The exclusions were listed at both 1.1.0 and 1.2.0 for the install that rewrote the lockfile, because pnpm verifies the committed lockfile against the release-age policy before resolving anything, then cut back to 1.2.0 and confirmed with --frozen-lockfile. Lockfile diff is 17 lines each way and touches nothing but the three packages. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
1.2.0 documents that `oxlint --print-config` renders an `extends`-shaped config post-expansion and pre-merge, so the `"ignorePatterns": []` it prints is not evidence of what would actually run. The conclusion here was right for a different reason: `extends` replaces the preset's ignorePatterns with the local array, and oxlint has no per-override ignore to put them back with. Config is unchanged. Claude-Session: https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1
|
Bumped onto the magic 1.2.0 round: Nothing in the release changes behaviour for this repo, and I checked rather than assumed:
One doc fix: the comment in The pnpm 11 exclusions were listed at both 1.1.0 and 1.2.0 for the single install that rewrote the lockfile, then cut back to 1.2.0 and confirmed with Green: install (clean + frozen), build, lint 0/0, |
Migrates the repo to the shared GSTJ/magic tooling stack.
Stack
.yarnrc→scripts/bootstrap.js, two lockfilespackageManagerpinned@react-native-community/eslint-configoxlint@1.75.0+magic-oxlint-config/react-nativeeslint-config-prettier+eslint-plugin-prettieroxfmt@0.60.0+magic-oxfmt-config(reactNative)tsconfig.jsonmagic-tsconfig/base.jsonGSTJ/magic/.github/workflows/ci.yml@mainrenovate.jsonextendinggithub>GSTJ/magicScripts are now the standard set:
lint,lint:fix,format,format:fix,typecheck, plusbuild,testand the existingrelease.typescriptis gone — it istypechecknow.The example app moved into the workspace
example/is a pnpm workspace package, which broke two things that assumed a privateexample/node_modules:mainpointed atnode_modules/expo/AppEntry.js. Replaced with the documentedregisterRootComponententry,example/index.ts.metro.config.jsusedblockList+extraNodeModulesto stop a second copy of every peer dependency loading out of the repo root. One install means one copy, so it is now the plain Expo monorepo config (watchFolders+nodeModulesPaths).Verified by actually bundling it:
expo export --platform iosproduces a 1144-module bundle, resolvingreact-native-magic-toastthrough the babel alias tosrc/as before.Renames
5, applied with
magic-kebab --write(0 skipped, 0 conflicts, 0 needing review) and committed on their own:6 import specifiers rewritten.
example/App.tsxstays — thereact-nativepreset exempts it, because Expo'sAppEntry.jsimports../../Appfrom insidenode_modules.Violations fixed
18 diagnostics on the first run. Beyond the 5 filename ones:
react-native/no-color-literals(5) — the library's three colours move tosrc/colors.ts, the example app's two to a const at the top ofApp.tsx. Thefill="white"icon props use the same constant.typescript/consistent-type-imports(5) — autofixed to top-levelimport type.react/function-component-definition(1) —example/App.tsxis an arrow function with a separate default export.jest/valid-title(2) — "renders alerts correctly" → "renders an alert toast". Snapshot keys were renamed in place rather than regenerated, sojest --cipassing proves the rendered output is byte-identical.Then
oxfmt .across the repo. Most of that diff is double quotes (the oldprettierblock setsingleQuote: true) andpackage.jsonkey sorting, which oxfmt does by default. Nopackage.jsonvalue changed except thekeywordsarray order.One local workaround
oxlint.config.mtscasts the imported preset:extends: [reactNative as OxlintConfig].magic-oxlint-config@1.0.0typesoverrides[].pluginsasstring[], while oxlint 1.75.0 narrowsOxlintOverride["plugins"]to a union of known plugin names, so the README'sdefineConfig({ extends: [reactNative] })failstsc --noEmitwith TS2322. Runtime is fine — oxlint loads the config and reports the preset's rules. Reported upstream; the cast comes out when the types line up.Checks
pnpm install --frozen-lockfilefrom a clean tree,pnpm run build,pnpm run lint(0 diagnostics),pnpm run format(clean),pnpm run typecheck,pnpm test(2 passed, 2 snapshots), plustsc --noEmitinsideexample/and theexpo exportabove. All green.Note, unrelated and left alone: the jest
moduleNameMappermaps\.svgto<rootDir>/src/__mocks__/svgMock.ts, a file that does not exist onmastereither. Nothing imports an.svg— the icons are inlinereact-native-svgcomponents — so it never fires.https://claude.ai/code/session_01Fe92vvdc4R3F4BDBFoLcw1