Skip to content

Commit

Permalink
Apply review suggestions
Browse files Browse the repository at this point in the history
  • Loading branch information
khamilowicz committed May 13, 2024
1 parent 7ff63d7 commit a3dcacf
Show file tree
Hide file tree
Showing 14 changed files with 138 additions and 117 deletions.
2 changes: 1 addition & 1 deletion packages/build-tools/src/customBuildContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
} from '@expo/eas-build-job';
import { bunyan } from '@expo/logger';
import {
ExternalBuildContextProvider,
BuildRuntimePlatform,
DynamicCacheManager,
ExternalBuildContextProvider,
} from '@expo/steps';

import { ArtifactToUpload, BuildContext, CacheManager } from './context';
Expand Down
37 changes: 19 additions & 18 deletions packages/build-tools/src/steps/easFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@ import { BuildFunction } from '@expo/steps';

import { CustomBuildContext } from '../customBuildContext';

import { createUploadArtifactBuildFunction } from './functions/uploadArtifact';
import { createRestoreCacheBuildFunction, createSaveCacheBuildFunction } from './functions/cache';
import { calculateEASUpdateRuntimeVersionFunction } from './functions/calculateEASUpdateRuntimeVersion';
import { createCheckoutBuildFunction } from './functions/checkout';
import { createSetUpNpmrcBuildFunction } from './functions/useNpmToken';
import { createInstallNodeModulesBuildFunction } from './functions/installNodeModules';
import { createPrebuildBuildFunction } from './functions/prebuild';
import { createFindAndUploadBuildArtifactsBuildFunction } from './functions/findAndUploadBuildArtifacts';
import { createSaveCacheBuildFunction, createRestoreCacheBuildFunction } from './functions/cache';
import { configureEASUpdateIfInstalledFunction } from './functions/configureEASUpdateIfInstalled';
import { injectAndroidCredentialsFunction } from './functions/injectAndroidCredentials';
import { configureAndroidVersionFunction } from './functions/configureAndroidVersion';
import { runGradleFunction } from './functions/runGradle';
import { resolveAppleTeamIdFromCredentialsFunction } from './functions/resolveAppleTeamIdFromCredentials';
import { configureEASUpdateIfInstalledFunction } from './functions/configureEASUpdateIfInstalled';
import { configureIosCredentialsFunction } from './functions/configureIosCredentials';
import { configureIosVersionFunction } from './functions/configureIosVersion';
import { createFindAndUploadBuildArtifactsBuildFunction } from './functions/findAndUploadBuildArtifacts';
import { generateGymfileFromTemplateFunction } from './functions/generateGymfileFromTemplate';
import { runFastlaneFunction } from './functions/runFastlane';
import { createStartAndroidEmulatorBuildFunction } from './functions/startAndroidEmulator';
import { createStartIosSimulatorBuildFunction } from './functions/startIosSimulator';
import { createInstallMaestroBuildFunction } from './functions/installMaestro';
import { createGetCredentialsForBuildTriggeredByGithubIntegration } from './functions/getCredentialsForBuildTriggeredByGitHubIntegration';
import { injectAndroidCredentialsFunction } from './functions/injectAndroidCredentials';
import { createInstallMaestroBuildFunction } from './functions/installMaestro';
import { createInstallNodeModulesBuildFunction } from './functions/installNodeModules';
import { createInstallPodsBuildFunction } from './functions/installPods';
import { createSendSlackMessageFunction } from './functions/sendSlackMessage';
import { createPrebuildBuildFunction } from './functions/prebuild';
import { resolveAppleTeamIdFromCredentialsFunction } from './functions/resolveAppleTeamIdFromCredentials';
import { createResolveBuildConfigBuildFunction } from './functions/resolveBuildConfig';
import { calculateEASUpdateRuntimeVersionFunction } from './functions/calculateEASUpdateRuntimeVersion';
import { runFastlaneFunction } from './functions/runFastlane';
import { runGradleFunction } from './functions/runGradle';
import { createSendSlackMessageFunction } from './functions/sendSlackMessage';
import { createStartAndroidEmulatorBuildFunction } from './functions/startAndroidEmulator';
import { createStartIosSimulatorBuildFunction } from './functions/startIosSimulator';
import { createUploadArtifactBuildFunction } from './functions/uploadArtifact';
import { createSetUpNpmrcBuildFunction } from './functions/useNpmToken';

export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
const functions = [
Expand All @@ -34,8 +34,7 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
createSetUpNpmrcBuildFunction(),
createInstallNodeModulesBuildFunction(),
createPrebuildBuildFunction(),
createSaveCacheBuildFunction(ctx),
createRestoreCacheBuildFunction(ctx),

configureEASUpdateIfInstalledFunction(),
injectAndroidCredentialsFunction(),
configureAndroidVersionFunction(),
Expand All @@ -58,6 +57,8 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] {
if (ctx.hasBuildJob()) {
functions.push(
...[
createSaveCacheBuildFunction(ctx),
createRestoreCacheBuildFunction(ctx),
createFindAndUploadBuildArtifactsBuildFunction(ctx),
createResolveBuildConfigBuildFunction(ctx),
createGetCredentialsForBuildTriggeredByGithubIntegration(ctx),
Expand Down
9 changes: 5 additions & 4 deletions packages/build-tools/src/steps/functions/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {

import { CustomBuildContext } from '../../customBuildContext';

export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildFunction {
export function createRestoreCacheBuildFunction(ctx: CustomBuildContext<BuildJob>): BuildFunction {
return new BuildFunction({
namespace: 'eas',
id: 'restore-cache',
Expand Down Expand Up @@ -51,9 +51,10 @@ export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildF
return;
}

const job = stepsCtx.global.staticContext.job as BuildJob;
const cache = job.cache;
const { cache } = ctx.job;
if (!cache) {
stepsCtx.logger.warn('Cache is not available, skipping...');

return;
}

Expand All @@ -73,7 +74,7 @@ export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildF
});
}

export function createSaveCacheBuildFunction(ctx: CustomBuildContext): BuildFunction {
export function createSaveCacheBuildFunction(ctx: CustomBuildContext<BuildJob>): BuildFunction {
return new BuildFunction({
namespace: 'eas',
id: 'save-cache',
Expand Down
1 change: 0 additions & 1 deletion packages/steps/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ if [[ "$npm_lifecycle_event" == "prepack" ]]; then
echo 'Removing "dist_commonjs" and "dist_esm" folders...'
rm -rf dist_commonjs dist_esm
fi
rm -rf dist_commonjs dist_esm

echo 'Compiling TypeScript to JavaScript...'
node_modules/.bin/tsc --project tsconfig.build.json
Expand Down
1 change: 0 additions & 1 deletion packages/steps/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@
"license": "BUSL-1.1",
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/glob": "^8.1.0",
"@types/jest": "^29.5.11",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.get": "^4.4.9",
Expand Down
38 changes: 24 additions & 14 deletions packages/steps/src/BuildStepInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import callInputFunctionAsync from './inputFunctions.js';
import {
BUILD_STEP_FUNCTION_EXPRESSION_REGEXP,
BUILD_STEP_OR_BUILD_GLOBAL_CONTEXT_REFERENCE_REGEX,
iterateWithFunctions,
interpolateWithFunctionsAsync,
interpolateWithOutputs,
iterateWithFunctions,
} from './utils/template.js';

export enum BuildStepInputValueTypeName {
Expand Down Expand Up @@ -57,6 +57,18 @@ interface BuildStepInputParams<T extends BuildStepInputValueTypeName, R extends
stepDisplayName: string;
}

interface ValidateFunctionError {
error: string;
templateFunction?: never;
}

interface ValidateFunctionSuccess {
error?: never;
templateFunction: string;
}

type ValidateFunctionInput = ValidateFunctionError | ValidateFunctionSuccess;

export interface SerializedBuildStepInput<
T extends BuildStepInputValueTypeName = BuildStepInputValueTypeName,
R extends boolean = boolean,
Expand Down Expand Up @@ -109,7 +121,10 @@ export class BuildStepInput<
}

public isFunctionCall(): boolean {
return this.rawValue?.toString().match(BUILD_STEP_FUNCTION_EXPRESSION_REGEXP) !== null;
if (!this.rawValue) {
return false;
}
return this.rawValue.toString().match(BUILD_STEP_FUNCTION_EXPRESSION_REGEXP) !== null;
}

private requiresInterpolation(rawValue: any): rawValue is string {
Expand All @@ -120,22 +135,20 @@ export class BuildStepInput<
);
}

public validateFunctions(
fn: (error: string | null, f: string | null, args: string[]) => any
): BuildConfigError[] {
public validateFunctions(fn: (input: ValidateFunctionInput) => any): BuildConfigError[] {
const rawValue = this._value ?? this.defaultValue;
const errors = [];
if (this.requiresInterpolation(rawValue) && this.isFunctionCall()) {
try {
iterateWithFunctions(rawValue, (fun, args) => {
const error = fn(null, fun, args);
iterateWithFunctions(rawValue, (templateFunction) => {
const error = fn({ templateFunction });
if (error) {
errors.push(error);
}
});
} catch (e) {
if (e instanceof BuildConfigError) {
errors.push(fn(e.message, null, []));
errors.push(fn({ error: e.message }));
} else {
throw e;
}
Expand All @@ -147,12 +160,9 @@ export class BuildStepInput<
public async prepareValueAsync(): Promise<void> {
const rawValue = this._value ?? this.defaultValue;
if (this.requiresInterpolation(rawValue) && this.isFunctionCall()) {
this._computedValue = (await interpolateWithFunctionsAsync(
rawValue,
(fn: string, args: string[]) => {
return callInputFunctionAsync(fn, args, this.ctx);
}
)) as BuildStepInputValueType<T>;
this._computedValue = (await interpolateWithFunctionsAsync(rawValue, (templateFunction) => {
return callInputFunctionAsync(templateFunction, this.ctx);
})) as BuildStepInputValueType<T>;
}
}

Expand Down
27 changes: 17 additions & 10 deletions packages/steps/src/BuildWorkflowValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { BuildStep } from './BuildStep.js';
import { BuildStepInputValueTypeName } from './BuildStepInput.js';
import { BuildWorkflow } from './BuildWorkflow.js';
import { BuildConfigError, BuildWorkflowError } from './errors.js';
import * as inputFunctions from './inputFunctions.js';
import { validateInputFunction } from './inputFunctions.js';
import { duplicates } from './utils/expodash/duplicates.js';
import { nullthrows } from './utils/nullthrows.js';
import { findOutputPaths } from './utils/template.js';
Expand Down Expand Up @@ -77,22 +77,29 @@ export class BuildWorkflowValidator {

if (currentStepInput.isFunctionCall()) {
errors.push(
...currentStepInput.validateFunctions((error, fn, args) => {
...currentStepInput.validateFunctions(({ error, templateFunction }) => {
if (error) {
return new BuildConfigError(
`Input parameter "${currentStepInput.id}" for step "${currentStep.displayName}" ${error}.`
);
}
if (fn && fn in inputFunctions) {
if (!templateFunction) {
return null;
}
return new BuildConfigError(
`Input parameter "${currentStepInput.id}" for step "${
currentStep.displayName
}" is set to "\${ ${fn}(${args
.map((i) => `"${i}"`)
.join(',')}) }" which is not a valid build-in function name.`
);
try {
validateInputFunction(templateFunction);
} catch (err: any) {
if (err.message.startsWith('Invalid identifier')) {
return new BuildConfigError(
`Input parameter "${currentStepInput.id}" for step "${currentStep.displayName}" is set to "\${ ${templateFunction} }" which is not a valid built-in function name.`
);
} else {
return new BuildConfigError(
`Input parameter "${currentStepInput.id}" for step "${currentStep.displayName}" contains an error in expression '${templateFunction}': ${err.message}.`
);
}
}
return null;
})
);
}
Expand Down
12 changes: 6 additions & 6 deletions packages/steps/src/__tests__/BuildWorkflowValidator-test.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import assert from 'assert';

import { BuildFunction } from '../BuildFunction.js';
import { BuildRuntimePlatform } from '../BuildRuntimePlatform.js';
import { BuildStep, BuildStepFunction } from '../BuildStep.js';
import { BuildStepInput, BuildStepInputValueTypeName } from '../BuildStepInput.js';
import { BuildStepOutput } from '../BuildStepOutput.js';
import { BuildWorkflow } from '../BuildWorkflow.js';
import { BuildWorkflowValidator } from '../BuildWorkflowValidator.js';
import { BuildConfigError, BuildWorkflowError } from '../errors.js';
import { BuildRuntimePlatform } from '../BuildRuntimePlatform.js';
import { BuildFunction } from '../BuildFunction.js';

import { createGlobalContextMock } from './utils/context.js';
import { getErrorAsync } from './utils/error.js';
Expand Down Expand Up @@ -56,7 +56,7 @@ describe(BuildWorkflowValidator, () => {
expect(error.errors[0]).toBeInstanceOf(BuildConfigError);
expect(error.errors[0].message).toBe('Duplicated step IDs: "test1", "test3"');
});
test('input set to a non-allowed value', async () => {
test('input sdfet to a non-allowed value', async () => {
const ctx = createGlobalContextMock();

const id1 = 'test1';
Expand Down Expand Up @@ -235,10 +235,10 @@ describe(BuildWorkflowValidator, () => {
'Input parameter "id6" for step "step_id" is set to "${ wrong.aaa }" which is not of type "number" or is not step or context reference.'
);
expect((error as BuildWorkflowError).errors[4].message).toBe(
'Input parameter "id7" for step "step_id" is set to "${ invalidFunction("foo") }" which is not a valid build-in function name.'
'Input parameter "id7" for step "step_id" is set to "${ invalidFunction("foo") }" which is not a valid built-in function name.'
);
expect((error as BuildWorkflowError).errors[5].message).toBe(
'Input parameter "id8" for step "step_id" contains syntax error in "${ hashFiles("foo) }".'
'Input parameter "id8" for step "step_id" contains an error in expression \'hashFiles("foo)\': Unclosed quote after "foo)" at character 15.'
);
});
test('output from future step', async () => {
Expand Down Expand Up @@ -538,7 +538,7 @@ describe(BuildWorkflowValidator, () => {
);
});

test('non-existing function module', async () => {
test('non-existing function module', async () => {
const ctx = createGlobalContextMock({ runtimePlatform: BuildRuntimePlatform.LINUX });
const workflow = new BuildWorkflow(ctx, {
buildSteps: [],
Expand Down
2 changes: 1 addition & 1 deletion packages/steps/src/__tests__/fixtures/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@ build:
inputs:
key: cache-key
paths:
- src
- src
6 changes: 1 addition & 5 deletions packages/steps/src/cli/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ export class CliContextProvider implements ExternalBuildContextProvider {
public readonly projectSourceDirectory: string,
public readonly projectTargetDirectory: string,
public readonly defaultWorkingDirectory: string,
public readonly buildLogsDirectory: string,
public readonly buildDirectory: string,
public readonly projectRootDirectory: string
public readonly buildLogsDirectory: string
) {}
public get env(): BuildStepEnv {
return this._env;
Expand Down Expand Up @@ -54,8 +52,6 @@ async function runAsync(
relativeProjectDirectory,
relativeProjectDirectory,
relativeProjectDirectory,
relativeProjectDirectory,
relativeProjectDirectory,
relativeProjectDirectory
),
false
Expand Down
35 changes: 22 additions & 13 deletions packages/steps/src/inputFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ import path from 'path';
import fg from 'fast-glob';

import { BuildStepGlobalContext } from './BuildStepContext.js';
import { jsepEval } from './utils/jsepEval.js';

export async function hashFilesAsync(
ctx: BuildStepGlobalContext,
files: string[]
): Promise<string> {
async function hashFilesAsync(ctx: BuildStepGlobalContext, ...files: string[]): Promise<string> {
const hash = crypto.createHash('sha256');

await Promise.all(
Expand All @@ -33,17 +31,28 @@ export async function hashFilesAsync(
return hash.digest('hex');
}

export const hashFiles = hashFilesAsync;
function _getFunctions(ctx?: BuildStepGlobalContext): Record<string, (...args: any) => any> {
return {
hashFiles: (...args: any) => ctx && hashFilesAsync(ctx, ...args),
};
}

// Returns noop list of functions for evaluation by jsep.
function getFunctionsForValidation(): Record<string, (...args: any) => any> {
return _getFunctions();
}

function getFunctions(ctx: BuildStepGlobalContext): Record<string, (...args: any) => any> {
return _getFunctions(ctx);
}

export function validateInputFunction(templateFunction: string): string {
return jsepEval(templateFunction, getFunctionsForValidation());
}

export default async function callInputFunctionAsync(
fnName: string,
args: any[],
templateFunction: string,
ctx: BuildStepGlobalContext
): Promise<string> {
switch (fnName) {
case 'hashFiles':
return await hashFilesAsync(ctx, args);
default:
throw new Error(`Unknown input function: ${fnName}`);
}
return jsepEval(templateFunction, getFunctions(ctx));
}
Loading

0 comments on commit a3dcacf

Please sign in to comment.