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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ This is the log of notable changes to EAS CLI and related packages.
- [eas-cli] clean up error handling in local builds. ([#4105](https://github.com/expo/eas-cli/pull/4105) by [@douglowder](https://github.com/douglowder))
- [build-tools] Install ffmpeg when it is missing so Argent screen recording works in EAS Simulator sessions. ([#4110](https://github.com/expo/eas-cli/pull/4110) by [@szdziedzic](https://github.com/szdziedzic))
- [eas-cli] Stop simulator job runs when `eas simulator:start` is canceled before the session is ready. ([#4113](https://github.com/expo/eas-cli/pull/4113) by [@sjchmiela](https://github.com/sjchmiela))
- [eas-cli] Exclude store media downloaded by `eas metadata:pull` from the project archive. ([#4124](https://github.com/expo/eas-cli/pull/4124) by [@Nezz](https://github.com/Nezz))

### 🧹 Chores

Expand Down
5 changes: 3 additions & 2 deletions packages/eas-cli/src/metadata/apple/tasks/app-clip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import path from 'path';

import fetch from '../../../fetch';
import Log from '../../../log';
import { STORE_APP_CLIP_DIRECTORY } from '../../media';
import { logAsync } from '../../utils/log';
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';

Expand Down Expand Up @@ -180,7 +181,7 @@ export class AppClipTask extends AppleTask {
// so subsequent pushes don't try to delete the in-progress upload.
// Filename match in syncAppClipHeaderImageAsync will skip re-upload.
const fileName = headerImage.attributes.fileName || 'header.png';
headerImagePath = path.join('store', 'apple', 'app-clip', locale, fileName);
headerImagePath = path.join(STORE_APP_CLIP_DIRECTORY, locale, fileName);
}
}
config.setAppClipLocalizedInfo(locale, {
Expand Down Expand Up @@ -495,7 +496,7 @@ async function downloadAppClipHeaderImageAsync(
return null;
}

const targetDir = path.join(projectDir, 'store', 'apple', 'app-clip', locale);
const targetDir = path.join(projectDir, STORE_APP_CLIP_DIRECTORY, locale);
await fs.promises.mkdir(targetDir, { recursive: true });

const fileName = headerImage.attributes.fileName || 'header.png';
Expand Down
5 changes: 3 additions & 2 deletions packages/eas-cli/src/metadata/apple/tasks/previews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import path from 'path';

import fetch from '../../../fetch';
import Log from '../../../log';
import { STORE_PREVIEW_DIRECTORY } from '../../media';
import { logAsync } from '../../utils/log';
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
import { ApplePreviewConfig, ApplePreviews } from '../types';
Expand Down Expand Up @@ -105,7 +106,7 @@ export class PreviewsTask extends AppleTask {
// the broken ASC record.
const fileName = preview.attributes.fileName || 'preview.mp4';
const relativePath =
downloaded || path.join('store', 'apple', 'preview', localeCode, previewType, fileName);
downloaded || path.join(STORE_PREVIEW_DIRECTORY, localeCode, previewType, fileName);

if (preview.attributes.previewFrameTimeCode) {
previews[previewType] = {
Expand Down Expand Up @@ -309,7 +310,7 @@ async function downloadPreviewAsync(
}

// Create directory structure: store/apple/preview/{locale}/{previewType}/
const previewsDir = path.join(projectDir, 'store', 'apple', 'preview', locale, previewType);
const previewsDir = path.join(projectDir, STORE_PREVIEW_DIRECTORY, locale, previewType);
await fs.promises.mkdir(previewsDir, { recursive: true });

// Use original filename for matching during sync
Expand Down
5 changes: 3 additions & 2 deletions packages/eas-cli/src/metadata/apple/tasks/screenshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import path from 'path';

import fetch from '../../../fetch';
import Log from '../../../log';
import { STORE_SCREENSHOT_DIRECTORY } from '../../media';
import { logAsync } from '../../utils/log';
import { AppleTask, TaskDownloadOptions, TaskPrepareOptions, TaskUploadOptions } from '../task';
import { AppleScreenshots } from '../types';
Expand Down Expand Up @@ -98,7 +99,7 @@ export class ScreenshotsTask extends AppleTask {
// path) or warn and skip (if it doesn't).
const fileName =
screenshot.attributes.fileName || `${String(i + 1).padStart(2, '0')}.png`;
paths.push(path.join('store', 'apple', 'screenshot', localeCode, displayType, fileName));
paths.push(path.join(STORE_SCREENSHOT_DIRECTORY, localeCode, displayType, fileName));
}

if (paths.length > 0) {
Expand Down Expand Up @@ -300,7 +301,7 @@ async function downloadScreenshotAsync(
}

// Create directory structure: store/apple/screenshot/{locale}/{displayType}/
const screenshotsDir = path.join(projectDir, 'store', 'apple', 'screenshot', locale, displayType);
const screenshotsDir = path.join(projectDir, STORE_SCREENSHOT_DIRECTORY, locale, displayType);
await fs.promises.mkdir(screenshotsDir, { recursive: true });

// Use original filename for matching during sync
Expand Down
16 changes: 16 additions & 0 deletions packages/eas-cli/src/metadata/media.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/**
* Directories, relative to the project directory, that `eas metadata:pull`
* downloads store media to and that `eas metadata:push` uploads it from.
*
* They are only read by the CLI itself, so the media is left out of the copy of
* the project that is uploaded for a build.
*/
export const STORE_SCREENSHOT_DIRECTORY = 'store/apple/screenshot';
export const STORE_PREVIEW_DIRECTORY = 'store/apple/preview';
export const STORE_APP_CLIP_DIRECTORY = 'store/apple/app-clip';

export const STORE_MEDIA_DIRECTORIES = [
STORE_SCREENSHOT_DIRECTORY,
STORE_PREVIEW_DIRECTORY,
STORE_APP_CLIP_DIRECTORY,
];
23 changes: 23 additions & 0 deletions packages/eas-cli/src/vcs/__tests__/local-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ describe(Ignore, () => {
const ignore = await Ignore.createForCopyingAsync('/root');
expect(ignore.ignores('.git')).toBe(true);
});

it('ignores store media if copying', async () => {
vol.fromJSON({}, '/root');

const ignore = await Ignore.createForCopyingAsync('/root');
expect(ignore.ignores('store/apple/screenshot')).toBe(true);
expect(ignore.ignores('store/apple/screenshot/en-US/APP_IPHONE_67/01.png')).toBe(true);
expect(ignore.ignores('store/apple/preview/en-US/IPHONE_67/01.mp4')).toBe(true);
expect(ignore.ignores('store/apple/app-clip/en-US/header.png')).toBe(true);
expect(ignore.ignores('store.config.json')).toBe(false);
});

it('ignores store media if copying and .easignore is present', async () => {
vol.fromJSON(
{
'.easignore': 'aaa',
},
'/root'
);

const ignore = await Ignore.createForCopyingAsync('/root');
expect(ignore.ignores('store/apple/screenshot/en-US/APP_IPHONE_67/01.png')).toBe(true);
});
describe('for checking', () => {
it('does not necessarily ignore .git', async () => {
vol.fromJSON({}, '/root');
Expand Down
61 changes: 61 additions & 0 deletions packages/eas-cli/src/vcs/clients/__tests__/git.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,67 @@ describe('git', () => {
});
});

describe('store metadata media', () => {
const screenshot = 'store/apple/screenshot/en-US/APP_IPHONE_67/01.png';
const preview = 'store/apple/preview/en-US/IPHONE_67/01.mp4';

async function setupProjectWithStoreMediaAsync(): Promise<{
repoRoot: string;
vcs: GitClient;
}> {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
const vcs = new GitClient({
requireCommit: false,
maybeCwdOverride: repoRoot,
});

await fs.writeFile(`${repoRoot}/store.config.json`, '{}');
for (const media of [screenshot, preview]) {
await fs.mkdir(path.dirname(path.join(repoRoot, media)), { recursive: true });
await fs.writeFile(path.join(repoRoot, media), 'media');
}

return { repoRoot, vcs };
}

it('is not included when it is untracked', async () => {
const { repoRoot, vcs } = await setupProjectWithStoreMediaAsync();
await spawnAsync('git', ['add', 'store.config.json'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'add store config'], { cwd: repoRoot });

const repoClone = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(repoClone)).resolves.not.toThrow();
await expect(fs.stat(path.join(repoClone, screenshot))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(repoClone, preview))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(repoClone, 'store.config.json'))).resolves.not.toThrow();
});

it('is not included when it is committed', async () => {
const { repoRoot, vcs } = await setupProjectWithStoreMediaAsync();
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'add store media'], { cwd: repoRoot });

const repoClone = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(repoClone)).resolves.not.toThrow();
await expect(fs.stat(path.join(repoClone, screenshot))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(repoClone, preview))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(repoClone, 'store.config.json'))).resolves.not.toThrow();
});

it('is not included when the project has an .easignore', async () => {
const { repoRoot, vcs } = await setupProjectWithStoreMediaAsync();
await fs.writeFile(`${repoRoot}/.easignore`, 'something-else\n');
await spawnAsync('git', ['add', '.'], { cwd: repoRoot });
await spawnAsync('git', ['commit', '-m', 'add store media'], { cwd: repoRoot });

const repoClone = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await expect(vcs.makeShallowCopyAsync(repoClone)).resolves.not.toThrow();
await expect(fs.stat(path.join(repoClone, screenshot))).rejects.toThrow('ENOENT');
await expect(fs.stat(path.join(repoClone, 'store.config.json'))).resolves.not.toThrow();
});
});

it('does not include files that have been removed in the working directory', async () => {
const repoRoot = await fs.mkdtemp(path.join(os.tmpdir(), 'eas-cli-git-test-'));
await spawnAsync('git', ['init'], { cwd: repoRoot });
Expand Down
8 changes: 6 additions & 2 deletions packages/eas-cli/src/vcs/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import createIgnore, { Ignore as SingleFileIgnore } from 'ignore';
import path from 'path';

import Log from '../log';
import { STORE_MEDIA_DIRECTORIES } from '../metadata/media';

export const EASIGNORE_FILENAME = '.easignore';
const GITIGNORE_FILENAME = '.gitignore';
Expand All @@ -16,7 +17,7 @@ const GITIGNORE_FILENAME = '.gitignore';
* Inconsistencies with git behavior:
* - if parent .gitignore has ignore rule and child has exception to that rule,
* file will still be ignored,
* - node_modules is always ignored,
* - node_modules and the store media directories are always ignored,
* - if .easignore exists, .gitignore files are not used.
*/
export class Ignore {
Expand All @@ -27,15 +28,18 @@ export class Ignore {
static async createForCopyingAsync(rootDir: string): Promise<Ignore> {
const ignore = new Ignore(rootDir);
await ignore.initIgnoreAsync({
// Store media is only read by `eas metadata:push`, which runs from the
// CLI, so there is no reason to upload it for a build.
defaultIgnore: `
.git
node_modules
${STORE_MEDIA_DIRECTORIES.join('\n')}
`,
});
return ignore;
}

/** Does not include the default .git and node_modules ignore rules. */
/** Does not include the default .git, node_modules and store media ignore rules. */
static async createForCheckingAsync(rootDir: string): Promise<Ignore> {
const ignore = new Ignore(rootDir);
await ignore.initIgnoreAsync({
Expand Down
Loading