UPDATE: Original title was How to tell EAS to use "npm ci" instead of "npm install" however, my assumptions in this post were wrong. See my second post below which explains the actual problem: expo Doctor’s behavior changes based on the release of external packages.
Original post below:
We started having issues with our builds in EAS today and after investigation I noticed that during the “Install Dependencies” step npm install is used:
Running "npm install" in /Users/expo/workingdir/build directory
This means our build is not reproducible; it appears that a minor version of a package we use was updated, thus breaking the build (even when being built from the same commit).
Why isn’t npm ci being used here? Is there a way to configure EAS build to use npm ci instead?
hi there! do you not commit your package-lock.json to git? npm install will use that as well. the result in a ci environment where your package.json and package-lock.json are consistent (ie: you’ve run npm install locally at least once since you last changed the contents of your package.json) then you npm ci and npm install will have the same output.
we use npm install as a default because it’s less prescriptive and because in some cases we may need to install dependencies during prebuild, although we are moving away from doing that in sdk 50.
our experimental custom builds supports swapping out any part of the build process, see the docs for more info. that said, i don’t think it’s worth using it in this case, given that what you describe should not be a problem, you can use a hook as a workaround for performing validation of your package-lock.json (add npm ci to eas-build-pre-install), and custom builds are still experimental.
Sorry for the confusion on this… we do commit our package-lock.json. After reading your post and investigating our issue, I do agree that there shouldn’t be a difference between npm install and npm ci in this case.
What we’re seeing is some unexpected behavior from npx expo-doctor, more specifically the expo install --check command that it delegates to.
First some background: we have a GitHub action that runs some continuous integration checks, one of them being npx expo-doctor. Recently this check was passing for a given commit, but today I noticed it was failing for the same commit. Because the check was for the same commit, by definition, nothing has changed on our end. This is what led me to believe it was an npm install issue. However, after looking at the source code for expo-cli I can see that the problem is within Expo itself.
The npx expo-doctor’s InstalledDependencyVersionCheck command delegates to expo install --check which then calls getBundledNativeModulesAsync as seen here:
Which then makes an API call to fetch a list of dependencies from an HTTP API:
In our case, it appears that a version of React Native was released recently, which caused the doctor command to start failing, even without changes on our end.
Some dependencies are incompatible with the installed expo version:
email@example.com - expected version: 0.71.13
Your project may not work correctly until you install the correct versions of the packages.
Install individual packages by running npx expo install firstname.lastname@example.org
Found outdated dependencies
The desired behavior here would be to have the command use getBundledNativeModulesFromExpoPackageAsync to get the package dependency list from the local .json file instead of requesting a list from the server. There seems to be fallback code for this case when the HTTP request fails, but what we’d like this to be the default behavior in a CI environment.
So it appears that we’d need to change the command to look for the CI environment variable or similar to achieve this. Without this change we’re unable to use npx expo-doctor as a part of our CI checks.
For now I can patch this myself using patch-package, but it seems like this could be useful for the community as well. I can open a GitHub issue if you agree.