mirror of
https://github.com/expo/eas-cli.git
synced 2026-11-15 03:38:08 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c663ae46df | ||
|
|
8d8a4c0314 |
No files matched your search
@@ -8,6 +8,8 @@ This is the log of notable changes to EAS CLI and related packages.
|
||||
|
||||
### 🎉 New features
|
||||
|
||||
- [build-tools] Add a `copy_before_upload` option to `eas/upload_artifact` to copy changing files before they are archived. ([#4164](https://github.com/expo/eas-cli/pull/4164) by [@sjchmiela](https://github.com/sjchmiela))
|
||||
|
||||
### 🐛 Bug fixes
|
||||
|
||||
### 🧹 Chores
|
||||
|
||||
@@ -5,12 +5,14 @@ import path from 'path';
|
||||
import { createGlobalContextMock } from '../../../__tests__/utils/context';
|
||||
import { createTestIosJob } from '../../../__tests__/utils/job';
|
||||
import { createMockLogger } from '../../../__tests__/utils/logger';
|
||||
import { BuildContext } from '../../../context';
|
||||
import { ArtifactToUpload, BuildContext } from '../../../context';
|
||||
import { CustomBuildContext } from '../../../customBuildContext';
|
||||
import { createUploadArtifactBuildFunction } from '../uploadArtifact';
|
||||
|
||||
describe(createUploadArtifactBuildFunction, () => {
|
||||
const contextUploadArtifact = jest.fn(async () => ({ artifactId: randomUUID() }));
|
||||
const contextUploadArtifact = jest.fn(async (_spec: { artifact: ArtifactToUpload }) => ({
|
||||
artifactId: randomUUID(),
|
||||
}));
|
||||
const ctx = new BuildContext(createTestIosJob({}), {
|
||||
env: {
|
||||
__API_SERVER_URL: 'http://api.expo.test',
|
||||
@@ -99,6 +101,43 @@ describe(createUploadArtifactBuildFunction, () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('copies artifacts to a temporary directory before uploading when requested', async () => {
|
||||
const globalContext = createGlobalContextMock({});
|
||||
const sourceDirectory = path.join(globalContext.defaultWorkingDirectory, 'live-logs');
|
||||
const sourceFile = path.join(sourceDirectory, 'emulator.log');
|
||||
let snapshotPath: string | undefined;
|
||||
|
||||
try {
|
||||
await fs.promises.mkdir(sourceDirectory, { recursive: true });
|
||||
await fs.promises.writeFile(sourceFile, 'log contents');
|
||||
contextUploadArtifact.mockImplementationOnce(async ({ artifact }) => {
|
||||
const [currentSnapshotPath] = artifact.paths;
|
||||
snapshotPath = currentSnapshotPath;
|
||||
expect(currentSnapshotPath).not.toBe(sourceDirectory);
|
||||
await expect(
|
||||
fs.promises.readFile(path.join(currentSnapshotPath, 'emulator.log'), 'utf-8')
|
||||
).resolves.toBe('log contents');
|
||||
return { artifactId: randomUUID() };
|
||||
});
|
||||
|
||||
const buildStep = uploadArtifact.createBuildStepFromFunctionCall(globalContext, {
|
||||
callInputs: {
|
||||
copy_before_upload: true,
|
||||
path: sourceDirectory,
|
||||
type: 'other',
|
||||
},
|
||||
});
|
||||
|
||||
await buildStep.executeAsync();
|
||||
|
||||
expect(snapshotPath).toBeDefined();
|
||||
await expect(fs.promises.access(snapshotPath!)).rejects.toThrow();
|
||||
await expect(fs.promises.readFile(sourceFile, 'utf-8')).resolves.toBe('log contents');
|
||||
} finally {
|
||||
await fs.promises.rm(sourceDirectory, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('does not throw for undefined type input', async () => {
|
||||
const buildStep = uploadArtifact.createBuildStepFromFunctionCall(createGlobalContextMock({}), {
|
||||
callInputs: {
|
||||
|
||||
@@ -11,6 +11,9 @@ import {
|
||||
BuildStepOutput,
|
||||
} from '@expo/steps';
|
||||
import assert from 'assert';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
|
||||
import { CustomBuildContext } from '../../customBuildContext';
|
||||
import { FindArtifactsError, findArtifacts } from '../../utils/artifacts';
|
||||
@@ -70,6 +73,12 @@ export function createUploadArtifactBuildFunction(ctx: CustomBuildContext): Buil
|
||||
required: false,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'copy_before_upload',
|
||||
required: false,
|
||||
defaultValue: false,
|
||||
allowedValueTypeName: BuildStepInputValueTypeName.BOOLEAN,
|
||||
}),
|
||||
BuildStepInput.createProvider({
|
||||
id: 'metadata',
|
||||
required: false,
|
||||
@@ -118,22 +127,29 @@ export function createUploadArtifactBuildFunction(ctx: CustomBuildContext): Buil
|
||||
throw result.reason;
|
||||
});
|
||||
|
||||
const artifactSpec = {
|
||||
type: parseArtifactTypeInput({
|
||||
platform: ctx.job.platform,
|
||||
inputValue: `${inputs.type.value ?? ''}`,
|
||||
}),
|
||||
paths: artifactPaths,
|
||||
name: (inputs.name.value || inputs.key.value) as string,
|
||||
};
|
||||
const artifact = isGenericArtifact(artifactSpec)
|
||||
? {
|
||||
...artifactSpec,
|
||||
metadata: inputs.metadata.value as Record<string, unknown> | undefined,
|
||||
}
|
||||
: artifactSpec;
|
||||
|
||||
const artifactType = parseArtifactTypeInput({
|
||||
platform: ctx.job.platform,
|
||||
inputValue: `${inputs.type.value ?? ''}`,
|
||||
});
|
||||
let artifactSnapshot: ArtifactSnapshot | null = null;
|
||||
try {
|
||||
if (inputs.copy_before_upload.value && artifactPaths.length > 0) {
|
||||
artifactSnapshot = await copyArtifactsToTemporaryDirectoryAsync(artifactPaths);
|
||||
logger.info('Copied artifacts to a temporary directory before upload.');
|
||||
}
|
||||
|
||||
const artifactSpec = {
|
||||
type: artifactType,
|
||||
paths: artifactSnapshot?.paths ?? artifactPaths,
|
||||
name: (inputs.name.value || inputs.key.value) as string,
|
||||
};
|
||||
const artifact = isGenericArtifact(artifactSpec)
|
||||
? {
|
||||
...artifactSpec,
|
||||
metadata: inputs.metadata.value as Record<string, unknown> | undefined,
|
||||
}
|
||||
: artifactSpec;
|
||||
|
||||
const { artifactId } = await ctx.runtimeApi.uploadArtifact({ artifact, logger });
|
||||
|
||||
if (artifactId) {
|
||||
@@ -141,17 +157,75 @@ export function createUploadArtifactBuildFunction(ctx: CustomBuildContext): Buil
|
||||
}
|
||||
} catch (error) {
|
||||
if (inputs.ignore_error.value) {
|
||||
logger.error({ err: error }, `Failed to upload ${artifact.type}. Ignoring error.`);
|
||||
logger.error({ err: error }, `Failed to upload ${artifactType}. Ignoring error.`);
|
||||
// Ignoring error.
|
||||
return;
|
||||
}
|
||||
|
||||
throw error;
|
||||
} finally {
|
||||
if (artifactSnapshot) {
|
||||
await fs.promises.rm(artifactSnapshot.directory, { force: true, recursive: true });
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
interface ArtifactSnapshot {
|
||||
directory: string;
|
||||
paths: string[];
|
||||
}
|
||||
|
||||
async function copyArtifactsToTemporaryDirectoryAsync(
|
||||
artifactPaths: string[]
|
||||
): Promise<ArtifactSnapshot> {
|
||||
const snapshotDirectory = await fs.promises.mkdtemp(
|
||||
path.join(os.tmpdir(), 'eas-upload-artifact-')
|
||||
);
|
||||
|
||||
try {
|
||||
const commonParentDirectory = getCommonParentDirectory(artifactPaths);
|
||||
const snapshotPaths = await Promise.all(
|
||||
artifactPaths.map(async artifactPath => {
|
||||
const snapshotPath = path.join(
|
||||
snapshotDirectory,
|
||||
path.relative(commonParentDirectory, artifactPath)
|
||||
);
|
||||
await fs.promises.mkdir(path.dirname(snapshotPath), { recursive: true });
|
||||
await fs.promises.cp(artifactPath, snapshotPath, {
|
||||
errorOnExist: true,
|
||||
force: false,
|
||||
recursive: true,
|
||||
});
|
||||
return snapshotPath;
|
||||
})
|
||||
);
|
||||
return { directory: snapshotDirectory, paths: snapshotPaths };
|
||||
} catch (error) {
|
||||
await fs.promises.rm(snapshotDirectory, { force: true, recursive: true });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function getCommonParentDirectory(artifactPaths: string[]): string {
|
||||
let commonParentDirectory = path.dirname(path.resolve(artifactPaths[0]));
|
||||
for (const artifactPath of artifactPaths.slice(1)) {
|
||||
const resolvedArtifactPath = path.resolve(artifactPath);
|
||||
while (
|
||||
resolvedArtifactPath !== commonParentDirectory &&
|
||||
!resolvedArtifactPath.startsWith(`${commonParentDirectory}${path.sep}`)
|
||||
) {
|
||||
const parentDirectory = path.dirname(commonParentDirectory);
|
||||
if (parentDirectory === commonParentDirectory) {
|
||||
break;
|
||||
}
|
||||
commonParentDirectory = parentDirectory;
|
||||
}
|
||||
}
|
||||
return commonParentDirectory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initially, upload_artifact supported application-archive and build-artifact.
|
||||
* Then, mistakenly, support for it was removed in favor of supporting ManagedArtifactType
|
||||
|
||||
Reference in new issue
Block a user