Skip to content
This repository was archived by the owner on May 4, 2026. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2025-04-14 - Replace readFileSync toString with utf8 encoding
**Learning:** In Node.js when reading files synchronously, reading it without encoding returns a Buffer which then converted to string with `.toString()` creates unnecessary intermediate memory allocations. When converting this using regex, you have to be careful as some files were already passing 'utf8' but still calling `.toString()`, which could result in invalid code like `readFileSync(file, 'utf8', 'utf8')`.
**Action:** Use specific regex matching or AST parsers to replace `.toString()` cleanly, ensuring that we either append `'utf8'` correctly or just strip the `.toString()` if the encoding was already provided.
2 changes: 1 addition & 1 deletion docs/ui/components/Snippet/DiffBlock.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { DiffBlock } from '.';
const dirname = path.dirname(fileURLToPath(import.meta.url));

const DIFF_PATH = '/static/diffs/expo-ios.diff';
const DIFF_CONTENT = fs.readFileSync(path.join(dirname, '../../../public', DIFF_PATH)).toString();
const DIFF_CONTENT = fs.readFileSync(path.join(dirname, '../../../public', DIFF_PATH), 'utf8');

const validateDiffContent = (screen: Screen) => {
expect(screen.getByText('ios/Podfile')).toBeInTheDocument();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ describe('devices info', () => {
const file = path.join(projectRoot, '.expo', 'devices.json');
expect(fs.existsSync(file)).toBe(true);

const { devices } = JSON.parse(fs.readFileSync(file, 'utf8').toString());
const { devices } = JSON.parse(fs.readFileSync(file, 'utf8'));
expect(devices.length).toBe(1);
expect(devices[0].installationId).toBe('test-device-id');
});
Expand Down
4 changes: 2 additions & 2 deletions packages/@expo/cli/src/utils/mergeGitIgnorePaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export function mergeGitIgnorePaths(
return null;
}

const targetGitIgnore = fs.readFileSync(targetGitIgnorePath).toString();
const sourceGitIgnore = fs.readFileSync(sourceGitIgnorePath).toString();
const targetGitIgnore = fs.readFileSync(targetGitIgnorePath, 'utf8');
const sourceGitIgnore = fs.readFileSync(sourceGitIgnorePath, 'utf8');
const merged = mergeGitIgnoreContents(targetGitIgnore, sourceGitIgnore);
// Only rewrite the file if it was modified.
if (merged.contents) {
Expand Down
4 changes: 2 additions & 2 deletions packages/@expo/config-plugins/src/android/Package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ export async function renameJniOnDiskForType({
filesToUpdate.forEach((filepath: string) => {
try {
if (fs.lstatSync(filepath).isFile() && ['.h', '.cpp'].includes(path.extname(filepath))) {
let contents = fs.readFileSync(filepath).toString();
let contents = fs.readFileSync(filepath, 'utf8');
contents = contents.replace(
new RegExp(transformJavaClassDescriptor(currentPackageName).replace(/\//g, '\\/'), 'g'),
transformJavaClassDescriptor(packageName)
Expand Down Expand Up @@ -207,7 +207,7 @@ export async function renamePackageOnDiskForType({
filesToUpdate.forEach((filepath: string) => {
try {
if (fs.lstatSync(filepath).isFile()) {
let contents = fs.readFileSync(filepath).toString();
let contents = fs.readFileSync(filepath, 'utf8');
if (path.extname(filepath) === '.kt') {
contents = replacePackageName(contents, currentPackageName, kotlinSanitizedPackageName);
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('e2e: Android locales', () => {
},
};
const mockJSONFile = {
readAsync: (path) => JSON.parse(vol.readFileSync(path).toString()),
readAsync: (path) => JSON.parse(vol.readFileSync(path, 'utf8')),
};
jest.mock('../../utils/XML', () => mockXML);
jest.mock('@expo/json-file', () => mockJSONFile);
Expand All @@ -84,21 +84,21 @@ describe('e2e: Android locales', () => {
{ projectRoot }
);

expect(vol.readFileSync('/app/android/app/src/main/res/values-b+es/strings.xml').toString())
expect(vol.readFileSync('/app/android/app/src/main/res/values-b+es/strings.xml', 'utf8'))
.toMatchInlineSnapshot(`
"<resources>
<string name="CFBundleDisplayName">"spanish-name"</string>
</resources>"
`);
// backwards compatibility
expect(vol.readFileSync('/app/android/app/src/main/res/values-b+en/strings.xml').toString())
expect(vol.readFileSync('/app/android/app/src/main/res/values-b+en/strings.xml', 'utf8'))
.toMatchInlineSnapshot(`
"<resources>
<string name="CFBundleDisplayName">"us-name"</string>
<string name="app_name">"us-name"</string>
</resources>"
`);
expect(vol.readFileSync('/app/android/app/src/main/res/values-b+en+US/strings.xml').toString())
expect(vol.readFileSync('/app/android/app/src/main/res/values-b+en+US/strings.xml', 'utf8'))
.toMatchInlineSnapshot(`
"<resources>
<string name="app_name">"us-name"</string>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,24 +44,24 @@ public class SomeClass {
await renamePackageOnDisk({ android: { package: 'xyz.bront.app' } }, '/myapp');
const mainActivityPath = '/myapp/android/app/src/main/java/xyz/bront/app/MainActivity.java';
expect(fs.existsSync(mainActivityPath)).toBeTruthy();
expect(fs.readFileSync(mainActivityPath).toString()).toMatch('package xyz.bront.app');
expect(fs.readFileSync(mainActivityPath, 'utf8')).toMatch('package xyz.bront.app');

const nestedClassPath =
'/myapp/android/app/src/main/java/xyz/bront/app/example/SomeClass.java';
expect(fs.existsSync(nestedClassPath)).toBeTruthy();
expect(fs.readFileSync(nestedClassPath).toString()).toMatch('package xyz.bront.app');
expect(fs.readFileSync(nestedClassPath).toString()).not.toMatch('com.lololol');
expect(fs.readFileSync(nestedClassPath, 'utf8')).toMatch('package xyz.bront.app');
expect(fs.readFileSync(nestedClassPath, 'utf8')).not.toMatch('com.lololol');

const buckPath = '/myapp/android/app/BUCK';
expect(fs.readFileSync(buckPath).toString()).toMatch('package = "xyz.bront.app"');
expect(fs.readFileSync(buckPath).toString()).not.toMatch('com.lololol');
expect(fs.readFileSync(buckPath, 'utf8')).toMatch('package = "xyz.bront.app"');
expect(fs.readFileSync(buckPath, 'utf8')).not.toMatch('com.lololol');
});

it('does not clobber itself if package has similar parts', async () => {
await renamePackageOnDisk({ android: { package: 'com.bront' } }, '/myapp');
const mainActivityPath = '/myapp/android/app/src/main/java/com/bront/MainActivity.java';
expect(fs.existsSync(mainActivityPath)).toBeTruthy();
expect(fs.readFileSync(mainActivityPath).toString()).toMatch('package com.bront');
expect(fs.readFileSync(mainActivityPath, 'utf8')).toMatch('package com.bront');
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ describe('FileHookTransform', () => {

it('should call hook function from createFingerprintAsync', async () => {
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8').toString());
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8'));
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
const options = await normalizeOptionsAsync('/app', { fileHookTransform: mockHook });
await createFingerprintAsync('/app', options);
Expand All @@ -121,7 +121,7 @@ describe('FileHookTransform', () => {
}
) as jest.MockedFunction<FileHookTransformFunction>;
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8').toString());
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8'));
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
const options = await normalizeOptionsAsync('/app', { fileHookTransform: mockExpoConfigHook });
const result = await createFingerprintAsync('/app', options);
Expand Down
79 changes: 77 additions & 2 deletions packages/@expo/fingerprint/src/__tests__/Fingerprint-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,36 @@ describe(diffFingerprintChangesAsync, () => {

expect(normalizedDiff).toMatchInlineSnapshot(`
[
{
"addedSource": {
"contents": "{"extraDependencies":[],"coreFeatures":[],"modules":[]}",
"debugInfo": {
"hash": "10c4144650e3af1f596683ac4ae7a6fd971f7447",
},
"hash": "10c4144650e3af1f596683ac4ae7a6fd971f7447",
"id": "expoAutolinkingConfig:android",
"reasons": [
"expoAutolinkingAndroid",
],
"type": "contents",
},
"op": "added",
},
{
"addedSource": {
"contents": "{"extraDependencies":[],"coreFeatures":[],"modules":[]}",
"debugInfo": {
"hash": "10c4144650e3af1f596683ac4ae7a6fd971f7447",
},
"hash": "10c4144650e3af1f596683ac4ae7a6fd971f7447",
"id": "expoAutolinkingConfig:ios",
"reasons": [
"expoAutolinkingIos",
],
"type": "contents",
},
"op": "added",
},
{
"addedSource": {
"contents": "{"android":{"adaptiveIcon":{"backgroundColor":"#FFFFFF","foregroundImage":"./assets/adaptive-icon.png"}},"assetBundlePatterns":["**/*"],"icon":"./assets/icon.png","ios":{"supportsTablet":true},"name":"sdk47","orientation":"portrait","platforms":["android","ios","web"],"slug":"sdk47","splash":{"backgroundColor":"#ffffff","image":"./assets/splash.png","resizeMode":"contain"},"updates":{"fallbackToCacheTimeout":0},"userInterfaceStyle":"light","version":"1.0.0","web":{"favicon":"./assets/favicon.png"}}",
Expand All @@ -86,13 +116,58 @@ describe(diffFingerprintChangesAsync, () => {
},
"op": "added",
},
{
"addedSource": {
"contents": "{"setup:docs":"./scripts/download-dependencies.sh","setup:native":"./scripts/download-dependencies.sh && ./scripts/setup-react-android.sh","postinstall":"yarn-deduplicate && yarn workspace @expo/cli prepare && patch-package && node ./tools/bin/expotools.js validate-workspace-dependencies","install:react-native-lab":"(([ \\"$(ls -A react-native-lab/react-native)\\" ] && (yarn --cwd react-native-lab/react-native install --frozen-lockfile || true)) || echo \\"Skipping installing Node modules in react-native-lab/react-native (directory empty)\\")","lint":"eslint .","tsc":"echo 'You are trying to run \\"tsc\\" in the workspace root. Run it from an individual package instead.' && exit 1"}",
"debugInfo": {
"hash": "c4f835988805180a373d4ff37cef6ce53cb14995",
},
"hash": "c4f835988805180a373d4ff37cef6ce53cb14995",
"id": "packageJson:scripts",
"reasons": [
"packageJson:scripts",
],
"type": "contents",
},
"op": "added",
},
{
"addedSource": {
"contents": "{}",
"debugInfo": {
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
},
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
"id": "rncoreAutolinkingConfig:android",
"reasons": [
"rncoreAutolinkingAndroid",
],
"type": "contents",
},
"op": "added",
},
{
"addedSource": {
"contents": "{}",
"debugInfo": {
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
},
"hash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f",
"id": "rncoreAutolinkingConfig:ios",
"reasons": [
"rncoreAutolinkingIos",
],
"type": "contents",
},
"op": "added",
},
]
`);
});

it('should return diff from contents changes', async () => {
vol.fromJSON(require('../sourcer/__tests__/fixtures/ExpoManaged47Project.json'));
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8').toString());
const packageJson = JSON.parse(vol.readFileSync('/app/package.json', 'utf8'));
jest.doMock('/app/package.json', () => packageJson, { virtual: true });
const fingerprint = await createFingerprintAsync(
'/app',
Expand Down Expand Up @@ -158,7 +233,7 @@ describe(diffFingerprintChangesAsync, () => {
'/app',
await normalizeOptionsAsync('/app', { debug: true })
);
const config = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const config = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
config.expo.jsEngine = 'jsc';
vol.writeFileSync('/app/app.json', JSON.stringify(config, null, 2));
const diff = await diffFingerprintChangesAsync(
Expand Down
14 changes: 7 additions & 7 deletions packages/@expo/fingerprint/src/sourcer/__tests__/Expo-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ describe(getExpoConfigSourcesAsync, () => {

it('should contain expo config', async () => {
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
const options = await normalizeOptionsAsync('/app');
const { config, loadedModules } = await getExpoConfigAsync('/app', options);
const sources = await getExpoConfigSourcesAsync('/app', config, loadedModules, options);
Expand All @@ -203,7 +203,7 @@ describe(getExpoConfigSourcesAsync, () => {
const { config, loadedModules } = await getExpoConfigAsync('/app', options);
const sources = await getExpoConfigSourcesAsync('/app', config, loadedModules, options);

const appJsonContents = vol.readFileSync('/app/app.json', 'utf8').toString();
const appJsonContents = vol.readFileSync('/app/app.json', 'utf8');
const appJson = JSON.parse(appJsonContents);
const { name } = appJson.expo;
// Re-insert name to change the object order
Expand All @@ -225,7 +225,7 @@ describe(getExpoConfigSourcesAsync, () => {

it('should transform expo config paths as relative paths', async () => {
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
appJson.expo.extra ||= {};
appJson.expo.extra.testFile = '/app/test-file.txt';
appJson.expo.extra.testNestedFile = '/app/nested/test-file.txt';
Expand Down Expand Up @@ -263,7 +263,7 @@ describe(getExpoConfigSourcesAsync, () => {
vol.writeFileSync('/app/assets/icon-light.png', 'PNG data');
vol.writeFileSync('/app/assets/icon-dark.png', 'PNG data');
vol.writeFileSync('/app/assets/icon-tinted.png', 'PNG data');
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
appJson.expo.ios ||= {};
appJson.expo.ios.icon = {
light: '/app/assets/icon-light.png',
Expand Down Expand Up @@ -299,7 +299,7 @@ describe(getExpoConfigSourcesAsync, () => {
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
vol.mkdirSync('/app/assets');
copyDirSync(path.join(__dirname, 'fixtures', 'ExpoGo.icon'), '/app/assets/ExpoGo.icon');
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
appJson.expo.ios ||= {};
appJson.expo.ios.icon = '/app/assets/ExpoGo.icon';
vol.writeFileSync('/app/app.json', JSON.stringify(appJson, null, 2));
Expand All @@ -319,7 +319,7 @@ describe(getExpoConfigSourcesAsync, () => {
it('should contain external google service files with override hash key', async () => {
vol.fromJSON(require('./fixtures/ExpoManaged47Project.json'));
vol.writeFileSync('/app/google-services.json', 'JSON data');
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString());
const appJson = JSON.parse(vol.readFileSync('/app/app.json', 'utf8'));
appJson.expo.android ||= {};
appJson.expo.android.googleServicesFile = '/app/google-services.json';
vol.writeFileSync('/app/app.json', JSON.stringify(appJson, null, 2));
Expand All @@ -342,7 +342,7 @@ describe(getExpoConfigSourcesAsync, () => {
vol.writeFileSync('/app/assets/images/splash-icon.png', 'PNG data');

const config = {
exp: JSON.parse(vol.readFileSync('/app/app.json', 'utf8').toString()).expo,
exp: JSON.parse(vol.readFileSync('/app/app.json', 'utf8')).expo,
};
const configResult = JSON.stringify({ config, loadedModules: [] });
const mockSpawnWithIpcAsync = spawnWithIpcAsync as jest.MockedFunction<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ describe('NpmPackageManager', () => {
await npm.addAsync(['expo@^46', 'react-native@0.69.3']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand All @@ -284,7 +284,7 @@ describe('NpmPackageManager', () => {
await npm.addAsync(['expo@^46', 'react-native@0.69.3', 'jest', '--ignore-scripts']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand All @@ -305,7 +305,7 @@ describe('NpmPackageManager', () => {
await npm.addAsync(['react-native@0.69.3', 'expo@next']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand All @@ -327,7 +327,7 @@ describe('NpmPackageManager', () => {
await npm.addAsync(['zebra@^1.0.0', 'Alpha@^2.0.0', 'beta@^3.0.0', 'Gamma@^4.0.0']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

const dependencyKeys = Object.keys(packageFile.dependencies);
Expand Down Expand Up @@ -384,7 +384,7 @@ describe('NpmPackageManager', () => {
await npm.addDevAsync(['expo@^46', 'react-native@0.69.3']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand All @@ -405,7 +405,7 @@ describe('NpmPackageManager', () => {
await npm.addDevAsync(['expo@^46', 'react-native@0.69.3', 'jest', '--ignore-scripts']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand All @@ -426,7 +426,7 @@ describe('NpmPackageManager', () => {
await npm.addDevAsync(['react-native@0.69.3', 'expo@next']);

const packageFile = JSON.parse(
vol.readFileSync(path.join(projectRoot, 'package.json')).toString()
vol.readFileSync(path.join(projectRoot, 'package.json'), 'utf8')
);

expect(packageFile).toHaveProperty(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe(setSplashScreenLegacyMainActivity, () => {
},
};
const mainActivity = await AndroidConfig.Paths.getMainActivityAsync('/app');
let contents = fs.readFileSync(mainActivity.path).toString();
let contents = fs.readFileSync(mainActivity.path, 'utf8');
contents = await setSplashScreenLegacyMainActivity(
exp,
{ backgroundColor: '#000020', resizeMode: 'native' },
Expand Down
Loading