Compare commits

...
7 changed files with 113 additions and 1 deletions

No files matched your search

+1
View File
@@ -8,6 +8,7 @@ This is the log of notable changes to EAS CLI and related packages.
### 🎉 New features
- [eas-cli] Include Git commit, commit message, and branch or tag metadata when creating simulator sessions. ([#4071](https://github.com/expo/eas-cli/pull/4071) by [@szdziedzic](https://github.com/szdziedzic))
- [build-tools] Collect and upload Argent tool-server session events during remote simulator sessions, mirroring agent-device event collection. ([#4067](https://github.com/expo/eas-cli/pull/4067) by [@szdziedzic](https://github.com/szdziedzic))
### 🐛 Bug fixes
@@ -22,6 +22,7 @@ import {
loadSimulatorEnvAsync,
resetSimulatorEnvAsync,
} from '../../../simulator/env';
import { Client } from '../../../vcs/vcs';
import SimulatorStart from '../start';
jest.mock('fs-extra');
@@ -74,6 +75,14 @@ const mockLoadSimulatorEnvAsync = jest.mocked(loadSimulatorEnvAsync);
const mockResetSimulatorEnvAsync = jest.mocked(resetSimulatorEnvAsync);
const mockOra = jest.mocked(ora);
const mockPromptAsync = jest.mocked(promptAsync);
const vcsClient = {
getCommitHashAsync: jest.fn(),
getLastCommitMessageAsync: jest.fn(),
getCurrentRefAsync: jest.fn(),
} as unknown as Client;
const mockGetCommitHashAsync = jest.mocked(vcsClient.getCommitHashAsync);
const mockGetLastCommitMessageAsync = jest.mocked(vcsClient.getLastCommitMessageAsync);
const mockGetCurrentRefAsync = jest.mocked(vcsClient.getCurrentRefAsync);
function makeCreatedDeviceRunSession(
overrides: Partial<CreatedDeviceRunSession> = {}
@@ -150,6 +159,9 @@ describe(SimulatorStart, () => {
mockLoadSimulatorEnvAsync.mockResolvedValue();
mockResetSimulatorEnvAsync.mockResolvedValue();
jest.mocked(fs.writeFile).mockResolvedValue(undefined as never);
mockGetCommitHashAsync.mockResolvedValue('0123456789abcdef0123456789abcdef01234567');
mockGetLastCommitMessageAsync.mockResolvedValue('Show Git context for simulator sessions');
mockGetCurrentRefAsync.mockResolvedValue('refs/heads/main');
});
afterAll(() => {
@@ -170,6 +182,7 @@ describe(SimulatorStart, () => {
loggedIn: { graphqlClient },
projectDir,
projectId: 'project-123',
vcsClient,
});
return { command, getContextAsync };
}
@@ -189,6 +202,9 @@ describe(SimulatorStart, () => {
});
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
gitCommitHash: '0123456789abcdef0123456789abcdef01234567',
gitCommitMessage: 'Show Git context for simulator sessions',
gitRef: 'refs/heads/main',
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -278,6 +294,9 @@ describe(SimulatorStart, () => {
);
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
gitCommitHash: '0123456789abcdef0123456789abcdef01234567',
gitCommitMessage: 'Show Git context for simulator sessions',
gitRef: 'refs/heads/main',
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -297,6 +316,9 @@ describe(SimulatorStart, () => {
);
expect(mockCreateDeviceRunSessionAsync).toHaveBeenCalledWith(graphqlClient, {
appId: 'project-123',
gitCommitHash: '0123456789abcdef0123456789abcdef01234567',
gitCommitMessage: 'Show Git context for simulator sessions',
gitRef: 'refs/heads/main',
packageVersion: undefined,
platform: AppPlatform.Ios,
type: DeviceRunSessionType.AgentDevice,
@@ -1,6 +1,7 @@
import { Flags } from '@oclif/core';
import nullthrows from 'nullthrows';
import { truncateGitCommitMessage } from '../../build/metadata';
import { getDeviceRunSessionUrl } from '../../build/utils/url';
import EasCommand from '../../commandUtils/EasCommand';
import { ExpoGraphqlClient } from '../../commandUtils/context/contextUtils/createGraphqlClient';
@@ -93,6 +94,7 @@ export default class SimulatorStart extends EasCommand {
...this.ContextOptions.ProjectId,
...this.ContextOptions.ProjectDir,
...this.ContextOptions.LoggedIn,
...this.ContextOptions.Vcs,
};
async runAsync(): Promise<void> {
@@ -106,6 +108,7 @@ export default class SimulatorStart extends EasCommand {
const {
projectId,
projectDir,
vcsClient,
loggedIn: { graphqlClient },
} = await this.getContextAsync(SimulatorStart, {
nonInteractive,
@@ -120,6 +123,12 @@ export default class SimulatorStart extends EasCommand {
}
const platform = await resolvePlatformAsync(flags.platform, nonInteractive);
const [gitCommitHash, lastCommitMessage, gitRef] = await Promise.all([
vcsClient.getCommitHashAsync(),
vcsClient.getLastCommitMessageAsync(),
vcsClient.getCurrentRefAsync(),
]);
const gitCommitMessage = truncateGitCommitMessage(lastCommitMessage ?? undefined) ?? null;
if (existingDeviceRunSessionId) {
Log.warn(
@@ -140,6 +149,9 @@ export default class SimulatorStart extends EasCommand {
type: DEVICE_RUN_SESSION_TYPE_BY_FLAG_VALUE[flags.type],
packageVersion: flags['package-version'],
maxRunTimeMinutes: flags['max-duration-minutes'],
gitCommitHash,
gitCommitMessage,
gitRef,
});
deviceRunSessionId = session.id;
nullthrows(session.turtleJobRun?.id, 'Expected simulator session to start');
@@ -10,10 +10,16 @@ import {
EnsureDeviceRunSessionStoppedMutationVariables,
} from '../generated';
type CreateDeviceRunSessionInputWithGitContext = CreateDeviceRunSessionInput & {
gitCommitHash?: string | null;
gitCommitMessage?: string | null;
gitRef?: string | null;
};
export const DeviceRunSessionMutation = {
async createDeviceRunSessionAsync(
graphqlClient: ExpoGraphqlClient,
deviceRunSessionInput: CreateDeviceRunSessionInput
deviceRunSessionInput: CreateDeviceRunSessionInputWithGitContext
): Promise<CreateDeviceRunSessionMutation['deviceRunSession']['createDeviceRunSession']> {
const data = await withErrorHandlingAsync(
graphqlClient
@@ -243,6 +243,47 @@ describe('git', () => {
).resolves.not.toThrow();
});
describe('getCurrentRefAsync', () => {
let repoRoot: string;
let vcs: GitClient;
beforeAll(async () => {
repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-ref-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
await spawnAsync('git', ['checkout', '-b', 'feature/device-sessions'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '--allow-empty', '-m', 'initial commit'], {
cwd: repoRoot,
});
vcs = new GitClient({
requireCommit: false,
maybeCwdOverride: repoRoot,
});
});
afterAll(async () => {
await fs.rm(repoRoot, { recursive: true, force: true });
});
it('returns a fully qualified branch ref', async () => {
await expect(vcs.getCurrentRefAsync()).resolves.toBe('refs/heads/feature/device-sessions');
});
it('returns a fully qualified tag ref for a detached checkout', async () => {
await spawnAsync('git', ['tag', 'v1.2.3'], { cwd: repoRoot });
await spawnAsync('git', ['checkout', '--detach', 'v1.2.3'], { cwd: repoRoot });
await expect(vcs.getCurrentRefAsync()).resolves.toBe('refs/tags/v1.2.3');
});
it('returns null for a detached checkout without an exact tag', async () => {
await spawnAsync('git', ['commit', '--allow-empty', '-m', 'detached commit'], {
cwd: repoRoot,
});
await expect(vcs.getCurrentRefAsync()).resolves.toBeNull();
});
});
describe('when requireCommit is true', () => {
it('adheres to .easignore', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
+24
View File
@@ -310,6 +310,30 @@ export default class GitClient extends Client {
}
}
public override async getCurrentRefAsync(): Promise<string | null> {
try {
const branchRef = (
await spawnAsync('git', ['symbolic-ref', '--quiet', 'HEAD'], {
cwd: this.maybeCwdOverride,
})
).stdout.trim();
if (branchRef) {
return branchRef;
}
} catch {}
try {
const tag = (
await spawnAsync('git', ['describe', '--tags', '--exact-match', 'HEAD'], {
cwd: this.maybeCwdOverride,
})
).stdout.trim();
return tag ? `refs/tags/${tag}` : null;
} catch {
return null;
}
}
public override async getLastCommitMessageAsync(): Promise<string | null> {
try {
return (
+6
View File
@@ -66,6 +66,12 @@ export abstract class Client {
return null;
}
// (optional) returns the fully qualified ref for the current checkout
// used for metadata - implementation can be safely skipped
public async getCurrentRefAsync(): Promise<string | null> {
return null;
}
// (optional) returns message of the last commit
// used for EAS Update - implementation can be safely skipped
public async getLastCommitMessageAsync(): Promise<string | null> {