Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[eas-cli] Adhere to .easignore even if requireCommit is true #2942

Merged
merged 5 commits into from
Mar 17, 2025
Merged
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ This is the log of notable changes to EAS CLI and related packages.

### 🛠 Breaking changes

- Add support for `.easignore` when `requireCommit` is set to `true`. ([#2942](https://github.com/expo/eas-cli/pull/2942) by [@sjchmiela](https://github.com/sjchmiela))
- Up to 15.0.0, if `requireCommit` was `true`, `.easignore` was silently ignored.
- Versions 15.0.0-15.0.13 started using `.easignore` to skip files from being bundled into a tarball when `requireCommit` was `true`. This was an unexpected change in behavior.
- To clear this up, versions 15.0.13-15.0.15 were erroring if `.easignore` was present when `requireCommit` was `true`.
- `[email protected]` formalizes the 15.0.0-15.0.13 behavior by adhering to `.easignore` even when `requireCommit` is set to `true`.
- If you know what you're doing and you want to suppress a warning printed, you can do so by setting `EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING` environment variable to `true`.

### 🎉 New features

### 🐛 Bug fixes
Expand Down
93 changes: 83 additions & 10 deletions packages/eas-cli/src/vcs/clients/__tests__/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import fs from 'fs/promises';
import os from 'os';
import path from 'path';

import Log from '../../../log';
import GitClient from '../git';

describe('git', () => {
Expand Down Expand Up @@ -242,19 +243,91 @@ describe('git', () => {
).resolves.not.toThrow();
});

it('does not allow .easignore if requireCommit is true', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
const vcs = new GitClient({
requireCommit: true,
maybeCwdOverride: repoRoot,
describe('when requireCommit is true', () => {
it('adheres to .easignore', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
const vcs = new GitClient({
requireCommit: true,
maybeCwdOverride: repoRoot,
});

const warn = jest.spyOn(Log, 'warn');

await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
await fs.writeFile(`${repoRoot}/.gitignore`, '*gitignored*\n');

await fs.writeFile(`${repoRoot}/easignored-file.txt`, 'file');
await fs.writeFile(`${repoRoot}/nonignored-file.txt`, 'file');
await fs.writeFile(`${repoRoot}/gitignored-file.txt`, 'file');
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'tmp commit'], { cwd: repoRoot });

const copyRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(copyRoot)).resolves.not.toThrow();

expect(warn).toHaveBeenCalledWith(expect.stringContaining('.easignore'));

await expect(fs.stat(path.join(copyRoot, 'easignored-file.txt'))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(copyRoot, 'gitignored-file.txt'))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(copyRoot, 'nonignored-file.txt'))).resolves.not.toThrow();
});

await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
it('prints a warning only once if .easignore exists', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
const vcs = new GitClient({
requireCommit: true,
maybeCwdOverride: repoRoot,
});

await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'tmp commit'], { cwd: repoRoot });
const warn = jest.spyOn(Log, 'warn');

await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'tmp commit'], { cwd: repoRoot });

const copyRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(copyRoot)).resolves.not.toThrow();

expect(warn).toHaveBeenCalledTimes(1);
expect(warn).toHaveBeenCalledWith(expect.stringContaining('.easignore'));

const anotherCopyRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(anotherCopyRoot)).resolves.not.toThrow();

expect(warn).toHaveBeenCalledTimes(1);
});

describe('when EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING is set', () => {
beforeAll(() => {
process.env.EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING = '1';
});

afterAll(() => {
delete process.env.EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING;
});

it('does not print a warning', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
const vcs = new GitClient({
requireCommit: true,
maybeCwdOverride: repoRoot,
});

await expect(vcs.makeShallowCopyAsync(repoRoot)).rejects.toThrow('Detected ".easignore" file');
const warn = jest.spyOn(Log, 'warn');
warn.mockClear();

await fs.writeFile(`${repoRoot}/.easignore`, '*easignored*\n');
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'tmp commit'], { cwd: repoRoot });

const copyRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(copyRoot)).resolves.not.toThrow();

expect(warn).toHaveBeenCalledTimes(0);
});
});
});
});
21 changes: 10 additions & 11 deletions packages/eas-cli/src/vcs/clients/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import spawnAsync from '@expo/spawn-async';
import { Errors } from '@oclif/core';
import chalk from 'chalk';
import fs from 'fs-extra';
import getenv from 'getenv';
import path from 'path';

import Log, { learnMore } from '../../log';
Expand All @@ -18,6 +19,8 @@ import {
import { EASIGNORE_FILENAME, Ignore, makeShallowCopyAsync } from '../local';
import { Client } from '../vcs';

let hasWarnedAboutEasignoreInRequireCommit = false;

export default class GitClient extends Client {
private readonly maybeCwdOverride?: string;
public requireCommit: boolean;
Expand Down Expand Up @@ -165,18 +168,14 @@ export default class GitClient extends Client {
const rootPath = await this.getRootPathAsync();
const sourceEasignorePath = path.join(rootPath, EASIGNORE_FILENAME);
const doesEasignoreExist = await fs.exists(sourceEasignorePath);
if (this.requireCommit && doesEasignoreExist) {
Log.error(
`".easignore" file is not supported if you also have "requireCommit" set to "true" in "eas.json".`
);
Log.error(
`Remove "${sourceEasignorePath}" and use ".gitignore" instead. ${learnMore(
'https://expo.fyi/eas-build-archive'
)}`
);
throw new Error(
`Detected ".easignore" file ${sourceEasignorePath} while in "requireCommit = true" mode.`
const shouldSuppressWarning =
hasWarnedAboutEasignoreInRequireCommit ||
getenv.boolish('EAS_SUPPRESS_REQUIRE_COMMIT_EASIGNORE_WARNING', false);
if (this.requireCommit && doesEasignoreExist && !shouldSuppressWarning) {
Log.warn(
`You have "requireCommit: true" in "eas.json" and also ".easignore". If ".easignore" does remove files, note that the repository checked out in EAS will not longer be Git-clean.`
);
hasWarnedAboutEasignoreInRequireCommit = true;
}

let gitRepoUri;
Expand Down