diff --git a/CHANGELOG.md b/CHANGELOG.md index 081a6ceb79..8a0a5ca504 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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`. + - `eas-cli@16.0.0` 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 diff --git a/packages/eas-cli/src/vcs/clients/__tests__/git.test.ts b/packages/eas-cli/src/vcs/clients/__tests__/git.test.ts index 4a120ff959..4e7fec8cdf 100644 --- a/packages/eas-cli/src/vcs/clients/__tests__/git.test.ts +++ b/packages/eas-cli/src/vcs/clients/__tests__/git.test.ts @@ -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', () => { @@ -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); + }); + }); }); }); diff --git a/packages/eas-cli/src/vcs/clients/git.ts b/packages/eas-cli/src/vcs/clients/git.ts index 5d0af67c02..0882a017e9 100644 --- a/packages/eas-cli/src/vcs/clients/git.ts +++ b/packages/eas-cli/src/vcs/clients/git.ts @@ -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'; @@ -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; @@ -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;