RORK LABJP
EVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobbleEVENT — Apple holds its Surprise and Shine event today, September 9, starting at 10:00 Pacific. That lands in the small hours of September 10 in JapanEXPECT — Expected are the iPhone 18 Pro and Pro Max, a foldable, the 2nm A20 Pro chip, and release dates for iOS 27 and its sibling updatesWAIT — As this is written the event has not happened yet. Rumor-stage writing and post-announcement writing look identical once they are mixed togetherMAX — Since Rork Max generates native Swift, Apple news is not somebody else's problem. Worth repeating that the standard product still writes React NativeSIMULATOR — Rork Max compiles on cloud Macs and lets you check the result in a streaming iOS simulator inside the browser, with no Xcode and no Mac hardwareSEASON — A new OS is when automated build pipelines wobble most. An article selling convenience owes its readers a word about that wobble
Articles/Getting Started
Getting Started/2026-08-29Beginner

Nine Expo patches shipped in thirty days. Which one is your app actually running?

A tilde in package.json does not mean you are on the newest patch. The file that decides what actually gets installed is package-lock.json. Here is what npm did when I ran the checks myself.

Expo203npm4Versioning2Rork558Troubleshooting39

One of my apps had been sitting with a slow-startup review for a while. When I heard that Expo had shipped a patch fixing a startup-time regression, I opened package.json expecting a quick win.

It said "expo": "~57.0.9". A tilde, so patches come along automatically — that was my assumption. I rebuilt without changing anything.

Nothing improved. The build was still on 57.0.9, nine patches behind.

I knew the range syntax well enough to recite it, and had never once checked what it actually resolves to in a project that was already running. As an indie developer juggling several apps at once, an assumption like that can sit undisturbed for months. This is what I found when I ran the checks properly.

Nine patches in thirty days

Before anything else, it helps to know how fast patches arrive. The npm registry stores publish timestamps, so you can ask it directly.

npm view expo time --json

Here are the last ten 57.0.x releases, as of 29 August 2026.

VersionPublished (UTC)
57.0.92026-07-29 19:30
57.0.102026-08-04 07:40
57.0.112026-08-06 11:24
57.0.122026-08-10 15:43
57.0.132026-08-14 14:25
57.0.142026-08-17 10:48
57.0.152026-08-20 10:50
57.0.162026-08-24 07:56
57.0.172026-08-26 20:02
57.0.182026-08-28 10:48

Nine releases between 29 July and 28 August. Thirty days, so roughly one patch every three days.

The practical consequence is that a patch number you noted down a few days ago is quite likely no longer the newest one. If you carry a number over from a blog post, a changelog you skimmed last week, or your own notes, your starting assumption may already be stale. Ask the registry each time instead.

The range in package.json does not decide what you get

Next, what does each range style actually resolve to? I made an empty project with nothing but expo in dependencies, and asked npm to generate only the lockfile.

npm install --package-lock-only

Five different specifiers, same day, same registry.

Specifier in package.jsonResolved version
57.0.957.0.9
~57.0.957.0.18
^57.0.957.0.18
57.0.x57.0.18
"*" (unconstrained)57.0.18

Tilde and caret both landed on 57.0.18, and so did the wildcard forms. While no new minor has been published, the difference between ~ and ^ simply does not surface.

The two are not equivalent, though. A tilde allows patch updates within the minor you named, so ~57.0.9 will accept 57.0.18 but stop before 57.1.0. A caret allows minor updates too, so ^57.0.9 would happily take 57.1.0 or 57.4.2 once those exist. Right now no 57.1.x has been published, which is the only reason both rows read the same. The day the first new minor lands, a project on a caret starts pulling in changes the tilde project will not see, without anyone editing a file. That is worth knowing before you decide which one you want in a project you ship from.

But notice what this table is really measuring: what npm picks when it resolves from scratch. It says nothing about what happens when you run npm install in a project that already has a lockfile — which is every real project.

The lockfile is what decides

So I ran the same check starting from an existing lockfile.

  1. Pin "expo": "57.0.9" and generate the lockfile.
  2. Edit package.json only, changing the range to "expo": "~57.0.9".
  3. Run npm install with the lockfile still in place.

The output:

step 1, pinned lock:        57.0.9
step 2, install with lock:  57.0.9
step 3, install without:    57.0.18

Step 2 is the whole answer. Even with ~57.0.9 written in package.json, npm keeps 57.0.9 as long as the locked version still satisfies the range. It has no reason to move, so it doesn't.

Step 3 is the same package.json with the lockfile deleted first. Now 57.0.18 goes in. Which means that if package-lock.json is not committed to Git, your machine and your build server can quietly install different versions of Expo from identical source. It is the same shape of problem as filenames that look identical on your Mac and differ on the build server, which I wrote about in Sunset.png and sunset.png: one file on your Mac, two on the build server.

If you exported your code from Rork and started developing locally, check that package-lock.json made it into your first commit. If it lands in your ignore file, your dependency versions drift on every build. Write .gitignore Before You Run git init on an Exported Rork Project covers the setup.

npm ci will tell you when they disagree

A mismatch between package.json and the lockfile gets silently reconciled by npm install. When you want to hear about it, use npm ci.

Here is the real output with the lockfile on 57.0.9 and package.json asking for ~57.0.15:

npm error code EUSAGE
npm error `npm ci` can only install packages when your package.json and
npm error package-lock.json or npm-shrinkwrap.json are in sync.
npm error Invalid: lock file's expo@57.0.9 does not satisfy expo@57.0.18

It stops. That refusal is exactly why build scripts and CI pipelines should use npm ci rather than npm install — failing at the point of disagreement costs far less than tracing a mystery difference days later.

When you just want to read the version currently locked, go straight to the file:

node -p "require('./package-lock.json').packages['node_modules/expo'].version"

Cloud build services follow the same rule. EAS Build installs from the lockfile in your repository, so a lockfile that has not moved since March produces a March build today, no matter what range sits above it in package.json. Nothing warns you, because from npm's point of view nothing is wrong.

If your project uses a different package manager the file name changes but the logic does not. Yarn writes yarn.lock, pnpm writes pnpm-lock.yaml, and both resolve from the lock first and the range second. Whichever one you have, that file is the honest record of your dependency versions, and package.json is closer to a statement of intent.

What to run when you do decide to upgrade

Upgrading is an explicit act. Name the version, and the lockfile updates along with it because the new specifier falls outside the locked range.

npm install expo@57.0.18
npx expo install --fix

The second line realigns the packages that Expo manages so they match the combination the SDK expects. Bumping expo alone and leaving everything else where it was gets you a set of versions nobody tested together.

One more thing if you keep native directories in your repository: running prebuild after an upgrade can wipe hand-edited native changes. Take an inventory before you upgrade rather than after — Find the native edits expo prebuild will erase before you upgrade to SDK 57 walks through it.

The ordering lesson generalises. Before you go hunting through your own code for a memory problem, confirm which versions you are actually running, which is the same argument I made in Count your worklets before you start deleting your own code.

Start here today

Close package.json and open package-lock.json instead. Read one line: the version under node_modules/expo. That single line tells you whether your app is at the front of the queue or nine patches behind it.

I never checked, and left a fixed regression sitting in one of my indie developer builds for a month. If this saves you that detour, it was worth writing down.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Getting Started2026-09-08
Whether You Need Rork Max Is Answered by Your Permissions, Not the Price Page
When I cannot decide between standard Rork and Rork Max, I stop reading the price page. Instead I pull every OS capability my app actually asks for out of app.json and Info.plist, then sort them into what Expo covers, what a config plugin covers, and what truly needs native Swift.
Getting Started2026-09-01
Starting with the EAS Observe free tier, and why Crashlytics stays installed
EAS Observe reached general availability on August 20. I looked into consolidating down to one monitoring service and found that native crashes are still out of scope. Here are the free-tier limits and the setup details that trip people up.
Getting Started2026-08-28
Sunset.png and sunset.png: one file on your Mac, two on the build server
An image that renders locally but breaks the cloud build. The cause was filename casing. Here is the reproduction, the git behavior behind it, and a small checker with measured results.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links