From a3dcacfafab73019614de96753a7829b64dc0fd2 Mon Sep 17 00:00:00 2001 From: Piotr Szeremeta Date: Mon, 13 May 2024 14:24:49 +0200 Subject: [PATCH] Apply review suggestions --- .../build-tools/src/customBuildContext.ts | 2 +- .../build-tools/src/steps/easFunctions.ts | 37 +++++++++--------- .../build-tools/src/steps/functions/cache.ts | 9 +++-- packages/steps/build.sh | 1 - packages/steps/package.json | 1 - packages/steps/src/BuildStepInput.ts | 38 +++++++++++------- packages/steps/src/BuildWorkflowValidator.ts | 27 ++++++++----- .../__tests__/BuildWorkflowValidator-test.ts | 12 +++--- .../steps/src/__tests__/fixtures/build.yml | 2 +- packages/steps/src/cli/cli.ts | 6 +-- packages/steps/src/inputFunctions.ts | 35 ++++++++++------- .../__tests__/runCustomFunction-test.ts | 8 ++-- .../src/utils/__tests__/template-test.ts | 39 ++++++++++--------- packages/steps/src/utils/template.ts | 38 ++++++++---------- 14 files changed, 138 insertions(+), 117 deletions(-) diff --git a/packages/build-tools/src/customBuildContext.ts b/packages/build-tools/src/customBuildContext.ts index 31010dc0..02901e31 100644 --- a/packages/build-tools/src/customBuildContext.ts +++ b/packages/build-tools/src/customBuildContext.ts @@ -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'; diff --git a/packages/build-tools/src/steps/easFunctions.ts b/packages/build-tools/src/steps/easFunctions.ts index 0f08c9b2..758d5ff6 100644 --- a/packages/build-tools/src/steps/easFunctions.ts +++ b/packages/build-tools/src/steps/easFunctions.ts @@ -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 = [ @@ -34,8 +34,7 @@ export function getEasFunctions(ctx: CustomBuildContext): BuildFunction[] { createSetUpNpmrcBuildFunction(), createInstallNodeModulesBuildFunction(), createPrebuildBuildFunction(), - createSaveCacheBuildFunction(ctx), - createRestoreCacheBuildFunction(ctx), + configureEASUpdateIfInstalledFunction(), injectAndroidCredentialsFunction(), configureAndroidVersionFunction(), @@ -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), diff --git a/packages/build-tools/src/steps/functions/cache.ts b/packages/build-tools/src/steps/functions/cache.ts index 81d0d406..f0323a6a 100644 --- a/packages/build-tools/src/steps/functions/cache.ts +++ b/packages/build-tools/src/steps/functions/cache.ts @@ -8,7 +8,7 @@ import { import { CustomBuildContext } from '../../customBuildContext'; -export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildFunction { +export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildFunction { return new BuildFunction({ namespace: 'eas', id: 'restore-cache', @@ -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; } @@ -73,7 +74,7 @@ export function createRestoreCacheBuildFunction(ctx: CustomBuildContext): BuildF }); } -export function createSaveCacheBuildFunction(ctx: CustomBuildContext): BuildFunction { +export function createSaveCacheBuildFunction(ctx: CustomBuildContext): BuildFunction { return new BuildFunction({ namespace: 'eas', id: 'save-cache', diff --git a/packages/steps/build.sh b/packages/steps/build.sh index eefc4996..13294e44 100755 --- a/packages/steps/build.sh +++ b/packages/steps/build.sh @@ -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 diff --git a/packages/steps/package.json b/packages/steps/package.json index 3583e107..5eed049d 100644 --- a/packages/steps/package.json +++ b/packages/steps/package.json @@ -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", diff --git a/packages/steps/src/BuildStepInput.ts b/packages/steps/src/BuildStepInput.ts index c67013c4..7b53186e 100644 --- a/packages/steps/src/BuildStepInput.ts +++ b/packages/steps/src/BuildStepInput.ts @@ -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 { @@ -57,6 +57,18 @@ interface BuildStepInputParams 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; } @@ -147,12 +160,9 @@ export class BuildStepInput< public async prepareValueAsync(): Promise { 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; + this._computedValue = (await interpolateWithFunctionsAsync(rawValue, (templateFunction) => { + return callInputFunctionAsync(templateFunction, this.ctx); + })) as BuildStepInputValueType; } } diff --git a/packages/steps/src/BuildWorkflowValidator.ts b/packages/steps/src/BuildWorkflowValidator.ts index e6fb3bd1..c3882097 100644 --- a/packages/steps/src/BuildWorkflowValidator.ts +++ b/packages/steps/src/BuildWorkflowValidator.ts @@ -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'; @@ -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; }) ); } diff --git a/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts b/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts index 5ed5d533..bda98844 100644 --- a/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts +++ b/packages/steps/src/__tests__/BuildWorkflowValidator-test.ts @@ -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'; @@ -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'; @@ -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 () => { @@ -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: [], diff --git a/packages/steps/src/__tests__/fixtures/build.yml b/packages/steps/src/__tests__/fixtures/build.yml index 5bae10f5..e3959fd1 100644 --- a/packages/steps/src/__tests__/fixtures/build.yml +++ b/packages/steps/src/__tests__/fixtures/build.yml @@ -33,4 +33,4 @@ build: inputs: key: cache-key paths: - - src \ No newline at end of file + - src diff --git a/packages/steps/src/cli/cli.ts b/packages/steps/src/cli/cli.ts index e0d02502..ede1f0bd 100644 --- a/packages/steps/src/cli/cli.ts +++ b/packages/steps/src/cli/cli.ts @@ -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; @@ -54,8 +52,6 @@ async function runAsync( relativeProjectDirectory, relativeProjectDirectory, relativeProjectDirectory, - relativeProjectDirectory, - relativeProjectDirectory, relativeProjectDirectory ), false diff --git a/packages/steps/src/inputFunctions.ts b/packages/steps/src/inputFunctions.ts index 2121449d..6a0643a8 100644 --- a/packages/steps/src/inputFunctions.ts +++ b/packages/steps/src/inputFunctions.ts @@ -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 { +async function hashFilesAsync(ctx: BuildStepGlobalContext, ...files: string[]): Promise { const hash = crypto.createHash('sha256'); await Promise.all( @@ -33,17 +31,28 @@ export async function hashFilesAsync( return hash.digest('hex'); } -export const hashFiles = hashFilesAsync; +function _getFunctions(ctx?: BuildStepGlobalContext): Record any> { + return { + hashFiles: (...args: any) => ctx && hashFilesAsync(ctx, ...args), + }; +} + +// Returns noop list of functions for evaluation by jsep. +function getFunctionsForValidation(): Record any> { + return _getFunctions(); +} + +function getFunctions(ctx: BuildStepGlobalContext): Record 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 { - switch (fnName) { - case 'hashFiles': - return await hashFilesAsync(ctx, args); - default: - throw new Error(`Unknown input function: ${fnName}`); - } + return jsepEval(templateFunction, getFunctions(ctx)); } diff --git a/packages/steps/src/scripts/__tests__/runCustomFunction-test.ts b/packages/steps/src/scripts/__tests__/runCustomFunction-test.ts index c2044883..2881c46c 100644 --- a/packages/steps/src/scripts/__tests__/runCustomFunction-test.ts +++ b/packages/steps/src/scripts/__tests__/runCustomFunction-test.ts @@ -1,20 +1,20 @@ -import path from 'path'; -import os from 'os'; import fs from 'fs/promises'; +import os from 'os'; +import path from 'path'; import { createContext } from 'this-file'; import { v4 as uuidv4 } from 'uuid'; import { BuildStepInput, BuildStepInputValueTypeName } from '../../BuildStepInput.js'; import { BuildStepOutput } from '../../BuildStepOutput.js'; -import { createStepContextMock } from '../../__tests__/utils/context.js'; import { cleanUpStepTemporaryDirectoriesAsync, createTemporaryEnvsDirectoryAsync, createTemporaryOutputsDirectoryAsync, } from '../../BuildTemporaryFiles.js'; -import { BIN_PATH } from '../../utils/shell/bin.js'; +import { createStepContextMock } from '../../__tests__/utils/context.js'; import { createCustomFunctionCall } from '../../utils/customFunction.js'; +import { BIN_PATH } from '../../utils/shell/bin.js'; describe('runCustomFunction', () => { test('can run custom function', async () => { diff --git a/packages/steps/src/utils/__tests__/template-test.ts b/packages/steps/src/utils/__tests__/template-test.ts index 75810ecf..24576e44 100644 --- a/packages/steps/src/utils/__tests__/template-test.ts +++ b/packages/steps/src/utils/__tests__/template-test.ts @@ -1,12 +1,12 @@ -import { BuildConfigError, BuildStepRuntimeError } from '../../errors.js'; import { getError } from '../../__tests__/utils/error.js'; +import { BuildConfigError, BuildStepRuntimeError } from '../../errors.js'; import { findOutputPaths, getObjectValueForInterpolation, + interpolateWithFunctionsAsync, interpolateWithGlobalContext, interpolateWithInputs, interpolateWithOutputs, - interpolateWithFunctionsAsync, parseOutputPath, } from '../template.js'; @@ -37,35 +37,38 @@ describe(interpolateWithOutputs, () => { describe(interpolateWithFunctionsAsync, () => { test('interpolation', async () => { - const nonArgs = await interpolateWithFunctionsAsync('foo${ noArgs() }bar', async (fn, args) => { - if (fn === 'noArgs' && args.length === 0) { - return 'ok'; + const nonArgs = await interpolateWithFunctionsAsync( + 'foo${ noArgs() }bar', + async (templateFunction) => { + if (templateFunction === 'noArgs()') { + return '-ok-'; + } + return templateFunction; } - return '${fn} | ${args.join(", ")}'; - }); + ); const oneArg = await interpolateWithFunctionsAsync( 'foo${ oneArg("src") }bar', - async (fn, args) => { - if (fn === 'oneArg' && args[0] === 'src') { - return 'ok'; + async (templateFunction) => { + if (templateFunction === 'oneArg("src")') { + return '-ok-'; } - return '${fn} | ${args.join(", ")}'; + return templateFunction; } ); const manyArgs = await interpolateWithFunctionsAsync( 'foo${ manyArgs("src", "hello") }bar', - async (fn, args) => { - if (fn === 'manyArgs' && args[0] === 'src' && args[1] === 'hello') { - return 'ok'; + async (templateFunction) => { + if (templateFunction === 'manyArgs("src", "hello")') { + return '-ok-'; } - return '${fn} | ${args.join(", ")}'; + return templateFunction; } ); - expect(nonArgs).toBe('foookbar'); - expect(oneArg).toBe('foookbar'); - expect(manyArgs).toBe('foookbar'); + expect(nonArgs).toBe('foo-ok-bar'); + expect(oneArg).toBe('foo-ok-bar'); + expect(manyArgs).toBe('foo-ok-bar'); }); }); diff --git a/packages/steps/src/utils/template.ts b/packages/steps/src/utils/template.ts index a8d09b7f..f091e8e6 100644 --- a/packages/steps/src/utils/template.ts +++ b/packages/steps/src/utils/template.ts @@ -1,5 +1,5 @@ -import get from 'lodash.get'; import cloneDeep from 'lodash.clonedeep'; +import get from 'lodash.get'; import { BuildStepInputValueTypeName } from '../BuildStepInput.js'; import { BuildConfigError, BuildStepRuntimeError } from '../errors.js'; @@ -8,7 +8,7 @@ import { nullthrows } from './nullthrows.js'; export const BUILD_STEP_INPUT_EXPRESSION_REGEXP = /\${\s*(inputs\.[\S]+)\s*}/; export const BUILD_STEP_OUTPUT_EXPRESSION_REGEXP = /\${\s*(steps\.[\S]+)\s*}/; -export const BUILD_STEP_FUNCTION_EXPRESSION_REGEXP = /\${\s*(?\w+)\((?.*)\)\s*}/; +export const BUILD_STEP_FUNCTION_EXPRESSION_REGEXP = /\${\s*((\w+)\((.*)\))\s*}/; export const BUILD_GLOBAL_CONTEXT_EXPRESSION_REGEXP = /\${\s*(eas\.[\S]+)\s*}/; export const BUILD_STEP_OR_BUILD_GLOBAL_CONTEXT_REFERENCE_REGEX = /\${\s*((steps|eas)\.[\S]+)\s*}/; @@ -27,17 +27,14 @@ export function templateFunctionsAndArgsIterator(templateString: string): Iterab return { next() { while ((functionCallMatch = regex.exec(templateString))) { - if (functionCallMatch?.groups) { - const templateFunction = functionCallMatch.groups['fun']; - try { - const args = JSON.parse(`[${functionCallMatch.groups['args']}]`.replace(/'/g, '"')); - return { done: false, value: { templateFunction, args, functionCallMatch } }; - } catch (e) { - if (e instanceof SyntaxError) { - throw new BuildConfigError(`contains syntax error in "${templateString}"`); - } - throw e; - } + if (functionCallMatch[1]) { + return { + done: false, + value: { + templateFunction: functionCallMatch[1], + matchedGroup: functionCallMatch[0], + }, + }; } } return { done: true, value: null }; @@ -49,25 +46,24 @@ export function templateFunctionsAndArgsIterator(templateString: string): Iterab export function iterateWithFunctions( templateString: string, - fn: (fn: string, args: string[]) => any + fn: (templateFunction: string) => any ): void { - const iterator = templateFunctionsAndArgsIterator(templateString); - for (const { templateFunction, args } of iterator) { - fn(templateFunction, args); + for (const { templateFunction } of templateFunctionsAndArgsIterator(templateString)) { + fn(templateFunction); } } export async function interpolateWithFunctionsAsync( templateString: string, - fn: (fn: string, args: string[]) => Promise + fn: (templateFunction: string) => Promise ): Promise { let result = templateString; const iterator = templateFunctionsAndArgsIterator(templateString); - for (const { templateFunction, args, functionCallMatch } of iterator) { - const value = await fn(templateFunction, args); - result = result.replace(functionCallMatch[0], value); + for (const { templateFunction, matchedGroup } of iterator) { + const value = await fn(templateFunction); + result = result.replace(matchedGroup, value); } return result; }