Skip to content
Open
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ This is the log of notable changes to EAS CLI and related packages.

### 🐛 Bug fixes

- [eas-cli] Write credentials downloaded to `credentials.json` into the project directory instead of the current working directory, so running `eas credentials` from a subdirectory no longer deletes the existing keystore, certificate or provisioning profile. ([#4477](https://github.com/expo/eas-cli/pull/4477) by [@breken-ai](https://github.com/breken-ai))

### 🧹 Chores

## [24.8.0](https://github.com/expo/eas-cli/releases/tag/v24.8.0) - 2026-09-24
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import fs from 'fs-extra';
import { vol } from 'memfs';
import path from 'path';
import prompts from 'prompts';

import { IosDistributionType } from '../../../graphql/generated';
Expand Down Expand Up @@ -115,6 +116,26 @@ describe('update credentials.json', () => {
expect(keystore).toEqual('c29tZWJpbmFyeWRhdGE='); // base64 "somebinarydata"
expect(newCredJson).toEqual(credJson);
});
it('should write the keystore to the project directory when it is not the current directory', async () => {
const projectDir = '/app';
const ctx = createCtxMock({ projectDir });
vol.fromJSON({
'/app/credentials.json': JSON.stringify({
android: {
keystore: {
keystorePath: 'keystore.jks',
keystorePassword: 'keystorePassword',
keyAlias: 'keyAlias',
keyPassword: 'keyPassword',
},
},
}),
'/app/keystore.jks': 'somebinarydata',
});
await updateAndroidCredentialsAsync(ctx, testLegacyAndroidBuildCredentialsFragment);
expect(await fs.readFile('/app/keystore.jks', 'base64')).toEqual(testKeystore.keystore);
expect(await fs.pathExists(path.join(process.cwd(), 'keystore.jks'))).toBe(false);
});
it('should update keystore and credentials.json if android part of credentials.json is not valid', async () => {
const ctx = createCtxMock();
vol.fromJSON({
Expand Down Expand Up @@ -257,6 +278,29 @@ describe('update credentials.json', () => {
},
});
});
it('should write ios credentials to the project directory when it is not the current directory', async () => {
const ctx = createCtxMock({
projectDir: '/app',
ios: {
...getNewIosApiMock(),
getIosAppCredentialsWithCommonFieldsAsync: jest.fn(
() => testCommonIosAppCredentialsFragment
),
},
});
vol.fromJSON({ '/app/package.json': '{}' });
const app = await getAppFromContextAsync(ctx);

await updateIosCredentialsAsync(ctx, app, targets, IosDistributionType.AppStore);

expect(await fs.readFile('/app/credentials/ios/dist-cert.p12', 'base64')).toEqual(
testDistCertFragmentNoDependencies.certificateP12
);
expect(await fs.readFile('/app/credentials/ios/profile.mobileprovision', 'base64')).toEqual(
testProvisioningProfileFragment.provisioningProfile
);
expect(await fs.pathExists(path.join(process.cwd(), 'credentials'))).toBe(false);
});
it('should not do anything if no credentials are returned from www', async () => {
const ctx = createCtxMock({
ios: {
Expand Down
4 changes: 2 additions & 2 deletions packages/eas-cli/src/credentials/credentialsJson/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ async function updateFileAsync(
await fs.remove(absolutePath);
}
if (base64Data) {
await fs.mkdirp(path.dirname(filePath));
await fs.writeFile(filePath, Buffer.from(base64Data, 'base64'));
await fs.mkdirp(path.dirname(absolutePath));
await fs.writeFile(absolutePath, Buffer.from(base64Data, 'base64'));
}
}

Expand Down
Loading