Skip to content
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features

- Better description for update:rollback. ([#3154](https://github.com/expo/eas-cli/pull/3154) by [@douglowder](https://github.com/douglowder))
- Support native server deployments in EAS Update ([#3155](https://github.com/expo/eas-cli/pull/3155) by [@krystofwoldrich](https://github.com/krystofwoldrich))

### 🐛 Bug fixes

Expand Down
58 changes: 57 additions & 1 deletion packages/eas-cli/src/commands/update/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import { jester } from '../../../credentials/__tests__/fixtures-constants';
import { UpdateFragment } from '../../../graphql/generated';
import { PublishMutation } from '../../../graphql/mutations/PublishMutation';
import { AppQuery } from '../../../graphql/queries/AppQuery';
import { collectAssetsAsync, uploadAssetsAsync } from '../../../project/publish';
import { buildBundlesAsync, collectAssetsAsync, uploadAssetsAsync } from '../../../project/publish';
import { getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync } from '../../../update/getBranchFromChannelNameAndCreateAndLinkIfNotExistsAsync';
import { getTemporaryPath, safelyDeletePathAsync } from '../../../utils/temporaryPath';
import { resolveVcsClient } from '../../../vcs';

const projectRoot = '/test-project';
Expand Down Expand Up @@ -65,6 +66,7 @@ jest.mock('../../../project/publish', () => ({
resolveInputDirectoryAsync: jest.fn((inputDir = 'dist') => path.join(projectRoot, inputDir)),
uploadAssetsAsync: jest.fn(),
}));
jest.mock('../../../utils/temporaryPath.ts');

describe(UpdatePublish.name, () => {
afterEach(() => {
Expand Down Expand Up @@ -177,6 +179,60 @@ describe(UpdatePublish.name, () => {
// Ensure non-public config is not present
expect(expoConfig).not.toHaveProperty('hooks');
});

it('creates a new update with a native deployment', async () => {
const flags = ['--non-interactive', '--branch=branch123', '--message=abc'];

const { appJson } = mockTestProject();
const { platforms, runtimeVersion } = mockTestExport();
jest.mocked(buildBundlesAsync).mockImplementation(() => {
// Mock config changing during the build
// e.g. server url deployment
appJson.expo.extra = {
...appJson.expo.extra,
afterBuild: 'mocked',
};

return Promise.resolve();
});

// Mock the temporary path for the generated config
const mockedGeneratedConfigTmpPath = `/tmp/test-${Math.random().toString(36).substring(2)}`;
jest.mocked(getTemporaryPath).mockReturnValueOnce(mockedGeneratedConfigTmpPath);

// Mock an existing branch, so we don't create a new one
jest.mocked(ensureBranchExistsAsync).mockResolvedValue({
branch: {
id: 'branch123',
name: 'wat',
},
createdBranch: false,
});

jest
.mocked(PublishMutation.publishUpdateGroupAsync)
.mockResolvedValue(platforms.map(platform => ({ ...updateStub, platform, runtimeVersion })));

await new UpdatePublish(flags, commandOptions).run();

// Pull the publish data from the mocked publish function
const publishData = jest.mocked(PublishMutation.publishUpdateGroupAsync).mock.calls[0][1][0];
// Pull the Expo config from the publish data
const expoConfig = nullthrows(publishData.updateInfoGroup).ios!.extra.expoClient;

expect(getTemporaryPath).toHaveBeenCalledTimes(1);
expect(safelyDeletePathAsync).toHaveBeenCalledTimes(1);
expect(safelyDeletePathAsync).toHaveBeenCalledWith(mockedGeneratedConfigTmpPath);

expect(buildBundlesAsync).toHaveBeenCalledWith(
expect.objectContaining({
extraEnv: expect.objectContaining({
__EXPO_GENERATED_CONFIG_PATH: mockedGeneratedConfigTmpPath,
}),
})
);
expect(expoConfig?.extra?.afterBuild).toEqual('mocked');
});
});

/** Create a new in-memory project, copied from src/commands/update/__tests__/republish.test.ts */
Expand Down
23 changes: 21 additions & 2 deletions packages/eas-cli/src/commands/update/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import uniqBy from '../../utils/expodash/uniqBy';
import formatFields from '../../utils/formatFields';
import { enableJsonOutput, printJsonOnlyOutput } from '../../utils/json';
import { maybeWarnAboutEasOutagesAsync } from '../../utils/statuspageService';
import { getTemporaryPath, safelyDeletePathAsync } from '../../utils/temporaryPath';

type RawUpdateFlags = {
auto: boolean;
Expand Down Expand Up @@ -167,6 +168,15 @@ export default class UpdatePublish extends EasCommand {
};

async runAsync(): Promise<void> {
const generatedConfigPath = getTemporaryPath();
try {
await this.runUnsafeAsync(generatedConfigPath);
} finally {
await safelyDeletePathAsync(generatedConfigPath);
}
}

private async runUnsafeAsync(generatedConfigPath: string): Promise<void> {
const { flags: rawFlags } = await this.parse(UpdatePublish);
const paginatedQueryOptions = getPaginatedQueryOptions(rawFlags);
const {
Expand Down Expand Up @@ -262,7 +272,10 @@ export default class UpdatePublish extends EasCommand {
exp,
platformFlag: requestedPlatform,
clearCache,
extraEnv: maybeServerEnv,
extraEnv: {
...maybeServerEnv,
__EXPO_GENERATED_CONFIG_PATH: generatedConfigPath,
},
});
bundleSpinner.succeed('Exported bundle(s)');
} catch (e) {
Expand Down Expand Up @@ -351,7 +364,13 @@ export default class UpdatePublish extends EasCommand {

uploadedAssetCount = uploadResults.uniqueUploadedAssetCount;
assetLimitPerUpdateGroup = uploadResults.assetLimitPerUpdateGroup;
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, exp);

const { exp: expAfterBuild } = await getDynamicPublicProjectConfigAsync({
env: {
__EXPO_GENERATED_CONFIG_PATH: generatedConfigPath,
},
});
unsortedUpdateInfoGroups = await buildUnsortedUpdateInfoGroupAsync(assets, expAfterBuild);

// NOTE(cedric): we assume that bundles are always uploaded, and always are part of
// `uploadedAssetCount`, perferably we don't assume. For that, we need to refactor the
Expand Down
12 changes: 9 additions & 3 deletions packages/eas-cli/src/project/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
truncateString as truncateUpdateMessage,
} from '../update/utils';
import { PresignedPost, uploadWithPresignedPostWithRetryAsync } from '../uploads';
import { easCliBin } from '../utils/easCli';
import {
expoCommandAsync,
shouldUseVersionedExpoCLI,
Expand Down Expand Up @@ -223,6 +224,11 @@ export async function buildBundlesAsync({
throw new Error('Could not locate package.json');
}

const extendedEnv = {
...extraEnv,
__EAS_BIN: easCliBin,
};

// Legacy global Expo CLI
if (!shouldUseVersionedExpoCLI(projectDir, exp)) {
await expoCommandAsync(
Expand All @@ -239,7 +245,7 @@ export async function buildBundlesAsync({
...(clearCache ? ['--clear'] : []),
],
{
extraEnv,
extraEnv: extendedEnv,
}
);
return;
Expand All @@ -265,7 +271,7 @@ export async function buildBundlesAsync({
...(clearCache ? ['--clear'] : []),
],
{
extraEnv,
extraEnv: extendedEnv,
}
);
return;
Expand Down Expand Up @@ -293,7 +299,7 @@ export async function buildBundlesAsync({
...(clearCache ? ['--clear'] : []),
],
{
extraEnv,
extraEnv: extendedEnv,
}
);
}
Expand Down
3 changes: 3 additions & 0 deletions packages/eas-cli/src/utils/easCli.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { argv } from 'node:process';

const packageJSON = require('../../package.json');

export const easCliVersion: string = packageJSON.version;
export const easCliBin: string = argv[1];
13 changes: 13 additions & 0 deletions packages/eas-cli/src/utils/temporaryPath.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import * as fs from 'node:fs/promises';
import * as os from 'node:os';
import * as path from 'node:path';

export function getTemporaryPath(): string {
return path.join(os.tmpdir(), Math.random().toString(36).substring(2));
}

export async function safelyDeletePathAsync(value: string): Promise<void> {
try {
await fs.rm(value, { recursive: true, force: true });
} catch {}
}
Loading