diff --git a/packages/amplify-category-function/src/commands/function/build.ts b/packages/amplify-category-function/src/commands/function/build.ts index 36edf0f76c5..3bc22a07eaf 100644 --- a/packages/amplify-category-function/src/commands/function/build.ts +++ b/packages/amplify-category-function/src/commands/function/build.ts @@ -1,7 +1,7 @@ import { $TSContext } from 'amplify-cli-core'; import { ServiceName } from '../..'; import { category } from '../../constants'; -import { ResourceMeta } from '../../provider-utils/awscloudformation/types/packaging-types'; +import { PackageRequestMeta } from '../../provider-utils/awscloudformation/types/packaging-types'; import { buildFunction } from '../../provider-utils/awscloudformation/utils/buildFunction'; import { packageResource } from '../../provider-utils/awscloudformation/utils/package'; @@ -39,5 +39,5 @@ export const run = async (context: $TSContext) => { }; const getSelectedResources = async (context: $TSContext, resourceName?: string) => { - return (await context.amplify.getResourceStatus(category, resourceName)).allResources as ResourceMeta[]; + return (await context.amplify.getResourceStatus(category, resourceName)).allResources as PackageRequestMeta[]; }; diff --git a/packages/amplify-category-function/src/index.ts b/packages/amplify-category-function/src/index.ts index b6945097977..e0a6b991c40 100644 --- a/packages/amplify-category-function/src/index.ts +++ b/packages/amplify-category-function/src/index.ts @@ -1,18 +1,20 @@ import path from 'path'; import { category } from './constants'; export { category } from './constants'; -import { FunctionBreadcrumbs, FunctionRuntimeLifecycleManager } from 'amplify-function-plugin-interface'; +import { BuildType, FunctionBreadcrumbs, FunctionRuntimeLifecycleManager } from 'amplify-function-plugin-interface'; import { $TSContext, pathManager, stateManager } from 'amplify-cli-core'; import sequential from 'promise-sequential'; import { updateConfigOnEnvInit } from './provider-utils/awscloudformation'; import { supportedServices } from './provider-utils/supported-services'; import _ from 'lodash'; -export { buildFunction } from './provider-utils/awscloudformation/utils/buildFunction'; +export { buildFunction, buildTypeKeyMap } from './provider-utils/awscloudformation/utils/buildFunction'; export { packageResource } from './provider-utils/awscloudformation/utils/package'; export { hashLayerResource } from './provider-utils/awscloudformation/utils/packageLayer'; import { ServiceName } from './provider-utils/awscloudformation/utils/constants'; export { ServiceName } from './provider-utils/awscloudformation/utils/constants'; import { isMultiEnvLayer } from './provider-utils/awscloudformation/utils/layerParams'; +import { buildFunction, buildTypeKeyMap } from './provider-utils/awscloudformation/utils/buildFunction'; +import { PackageRequestMeta } from './provider-utils/awscloudformation/types/packaging-types'; export { isMultiEnvLayer } from './provider-utils/awscloudformation/utils/layerParams'; export { askExecRolePermissionsQuestions } from './provider-utils/awscloudformation/service-walkthroughs/execPermissionsWalkthrough'; @@ -169,6 +171,13 @@ export async function getInvoker( }); } +export function getBuilder(context: $TSContext, resourceName: string, buildType: BuildType): () => Promise { + const lastBuildTimeStamp = _.get(stateManager.getMeta(), [category, resourceName, buildTypeKeyMap[buildType]]); + return async () => { + await buildFunction(context, { resourceName, buildType, lastBuildTimeStamp }); + }; +} + export function isMockable(context: any, resourceName: string): IsMockableResponse { const resourceValue = _.get(context.amplify.getProjectMeta(), [category, resourceName]); if (!resourceValue) { diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts index 8359f83d2a2..098958cb3ee 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts @@ -1,6 +1,6 @@ import { $TSContext, ResourceTuple } from 'amplify-cli-core'; -export type ResourceMeta = ResourceTuple & { +export type PackageRequestMeta = ResourceTuple & { service: string; build: boolean; distZipFilename: string; @@ -9,4 +9,4 @@ export type ResourceMeta = ResourceTuple & { skipHashing: boolean; }; -export type Packager = (context: $TSContext, resource: ResourceMeta) => Promise<{ zipFilename: string; zipFilePath: string }>; +export type Packager = (context: $TSContext, resource: PackageRequestMeta) => Promise<{ zipFilename: string; zipFilePath: string }>; diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts index fe50db280fc..5d773979290 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts @@ -1,11 +1,14 @@ import { $TSContext, pathManager } from 'amplify-cli-core'; import { FunctionRuntimeLifecycleManager, BuildRequest, BuildType } from 'amplify-function-plugin-interface'; -import { ResourceMeta } from '../types/packaging-types'; import * as path from 'path'; +import { category } from '../../../constants'; -export const buildFunction = async (context: $TSContext, resource: ResourceMeta, buildType: BuildType = BuildType.PROD) => { - const resourcePath = path.join(pathManager.getBackendDirPath(), resource.category, resource.resourceName); - const breadcrumbs = context.amplify.readBreadcrumbs(resource.category, resource.resourceName); +export const buildFunction = async ( + context: $TSContext, + { resourceName, lastBuildTimeStamp, buildType = BuildType.PROD }: BuildRequestMeta, +) => { + const resourcePath = path.join(pathManager.getBackendDirPath(), category, resourceName); + const breadcrumbs = context.amplify.readBreadcrumbs(category, resourceName); const runtimePlugin: FunctionRuntimeLifecycleManager = (await context.amplify.loadRuntimePlugin( context, @@ -14,11 +17,11 @@ export const buildFunction = async (context: $TSContext, resource: ResourceMeta, const depCheck = await runtimePlugin.checkDependencies(breadcrumbs.functionRuntime); if (!depCheck.hasRequiredDependencies) { - context.print.error(depCheck.errorMessage || `You are missing dependencies required to package ${resource.resourceName}`); - throw new Error(`Missing required dependencies to package ${resource.resourceName}`); + context.print.error(depCheck.errorMessage || `You are missing dependencies required to package ${resourceName}`); + throw new Error(`Missing required dependencies to package ${resourceName}`); } - const prevBuildTime = resource.lastBuildTimeStamp ? new Date(resource.lastBuildTimeStamp) : undefined; + const prevBuildTime = lastBuildTimeStamp ? new Date(lastBuildTimeStamp) : undefined; // build the function let rebuilt = false; @@ -32,16 +35,27 @@ export const buildFunction = async (context: $TSContext, resource: ResourceMeta, runtime: breadcrumbs.functionRuntime, legacyBuildHookParams: { projectRoot: pathManager.findProjectRoot(), - resourceName: resource.resourceName, + resourceName: resourceName, }, lastBuildTimeStamp: prevBuildTime, }; rebuilt = (await runtimePlugin.build(buildRequest)).rebuilt; } if (rebuilt) { - context.amplify.updateamplifyMetaAfterBuild(resource, buildType); + context.amplify.updateamplifyMetaAfterBuild({ category, resourceName }, buildType.toString()); return new Date().toISOString(); } else { - return resource?.lastBuildTimeStamp; + return lastBuildTimeStamp; } }; + +export interface BuildRequestMeta { + resourceName: string; + lastBuildTimeStamp?: string; + buildType?: BuildType; +} + +export const buildTypeKeyMap: Record = { + [BuildType.PROD]: 'lastBuildTimeStamp', + [BuildType.DEV]: 'lastDevBuildTimeStamp', +}; diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/functionPluginLoader.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/functionPluginLoader.ts index 2c366ec71a8..e213a605cd9 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/functionPluginLoader.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/functionPluginLoader.ts @@ -5,7 +5,6 @@ import { FunctionRuntimeCondition, FunctionRuntimeParameters, FunctionTemplateParameters, - Contributor, FunctionRuntimeLifecycleManager, RuntimeContributionRequest, TemplateContributionRequest, @@ -13,8 +12,7 @@ import { import { ServiceName } from './constants'; import _ from 'lodash'; import { LayerParameters } from './layerParams'; -import { ResourceMeta } from '../types/packaging-types'; -import { $TSContext, ResourceTuple } from 'amplify-cli-core'; +import { $TSContext } from 'amplify-cli-core'; import { category } from '../../../constants'; /* * This file contains the logic for loading, selecting and executing function plugins (currently runtime and template plugins) diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts index fc87d812d37..bc0e2719b06 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts @@ -1,19 +1,18 @@ -import { $TSContext } from 'amplify-cli-core'; import _ from 'lodash'; -import { Packager, ResourceMeta } from '../types/packaging-types'; +import { Packager } from '../types/packaging-types'; import { ServiceName } from './constants'; import { packageFunction } from './packageFunction'; import { packageLayer } from './packageLayer'; export const packageResource: Packager = async (context, resource) => getServicePackager(resource.service)(context, resource); +const getServicePackager = (service: string) => (servicePackagerMap[service] ?? packageUnknown) as Packager; + const servicePackagerMap: Record = { [ServiceName.LambdaFunction]: packageFunction, [ServiceName.LambdaLayer]: packageLayer, }; -const getServicePackager = (service: string) => - (servicePackagerMap[service] ?? - (() => { - throw new Error(`Unknown function service type ${service}`); - })) as Packager; +const packageUnknown: Packager = (_, resource) => { + throw new Error(`Unknown function service type ${resource.service}`); +}; diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageLayer.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageLayer.ts index 63c4ba8463f..33ff0777596 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageLayer.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageLayer.ts @@ -21,14 +21,14 @@ import { getLayerRuntimes } from './layerRuntimes'; import crypto from 'crypto'; import { updateLayerArtifacts } from './storeResources'; import globby from 'globby'; -import { Packager, ResourceMeta } from '../types/packaging-types'; +import { Packager, PackageRequestMeta } from '../types/packaging-types'; export const packageLayer: Packager = async (context, resource) => { await ensureLayerVersion(context, resource.resourceName); return zipLayer(context, resource); }; -async function zipLayer(context: $TSContext, resource: ResourceMeta) { +async function zipLayer(context: $TSContext, resource: PackageRequestMeta) { const zipFilename = 'latest-build.zip'; const layerName = resource.resourceName; const layerDirPath = path.join(pathManager.getBackendDirPath(), resource.category, layerName); diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/storeResources.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/storeResources.ts index 0bcabde2919..aa550ea5159 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/storeResources.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/storeResources.ts @@ -23,7 +23,7 @@ export function createFunctionResources(context: $TSContext, parameters: Functio copyTemplateFiles(context, parameters); saveMutableState(parameters); saveCFNParameters(parameters); - context.amplify.leaveBreadcrumbs(context, categoryName, parameters.resourceName, createBreadcrumbs(parameters)); + context.amplify.leaveBreadcrumbs(categoryName, parameters.resourceName, createBreadcrumbs(parameters)); } export const createLayerArtifacts = (context: $TSContext, parameters: LayerParameters, latestVersion: number = 1): string => { diff --git a/packages/amplify-cli-core/package.json b/packages/amplify-cli-core/package.json index 7b9c699627a..16747098b87 100644 --- a/packages/amplify-cli-core/package.json +++ b/packages/amplify-cli-core/package.json @@ -39,7 +39,7 @@ "@types/node": "^10.17.13", "@types/rimraf": "^3.0.0", "@types/uuid": "^8.0.0", - "amplify-function-plugin-interface": "1.4.1", + "amplify-function-plugin-interface": "1.6.0", "rimraf": "^3.0.0" }, "jest": { diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index 464ec5f60ef..8b1781f46b3 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -1,5 +1,4 @@ import { ServiceSelection } from './serviceSelection'; -import { BuildType } from 'amplify-function-plugin-interface'; export * from './cliContext'; export * from './cliContextEnvironmentProvider'; @@ -36,6 +35,21 @@ export type $TSContext = { runtime: $TSAny; pluginPlatform: IPluginPlatform; newUserInfo?: $TSAny; + filesystem: IContextFilesystem; + template: IContextTemplate; +}; + +export type IContextFilesystem = { + remove: (targetPath: string) => void; + read: (targetPath: string, encoding?: string) => $TSAny; + write: (targetPath: string, data: unknown) => void; + exists: (targetPath: string) => boolean; + isFile: (targetPath: string) => boolean; + path: (...pathParts: string[]) => string; +}; + +export type IContextTemplate = { + generate: (opts: { template: string; target: string; props: object; directory: string }) => string; }; export type IPluginPlatform = { @@ -125,12 +139,14 @@ export interface AmplifyProjectConfig { providers: string[]; } +export type $TSCopyJob = any; + // Temporary interface until Context refactor interface AmplifyToolkit { confirmPrompt: (prompt: string, defaultValue?: boolean) => boolean; constants: $TSAny; constructExeInfo: (context: $TSContext) => $TSAny; - copyBatch: (context: $TSContext, jobs: $TSAny, props: $TSAny, force: boolean, writeParams?: $TSAny[] | $TSObject) => Promise; + copyBatch: (context: $TSContext, jobs: $TSCopyJob[], props: object, force?: boolean, writeParams?: boolean | object) => $TSAny; crudFlow: () => $TSAny; deleteProject: () => $TSAny; executeProviderUtils: () => $TSAny; @@ -196,7 +212,8 @@ interface AmplifyToolkit { updateamplifyMetaAfterResourceDelete: (category: string, resourceName: string) => void; updateProvideramplifyMeta: (providerName: string, options: $TSObject) => void; updateamplifyMetaAfterPush: (resources: $TSObject[]) => void; - updateamplifyMetaAfterBuild: (resource: ResourceTuple, buildType?: BuildType) => void; + // buildType is from amplify-function-plugin-interface but can't be imported here because it would create a circular dependency + updateamplifyMetaAfterBuild: (resource: ResourceTuple, buildType?: string) => void; updateAmplifyMetaAfterPackage: (resource: ResourceTuple, zipFilename: string) => void; updateBackendConfigAfterResourceAdd: (category: string, resourceName: string, resourceData: $TSAny) => $TSAny; updateBackendConfigAfterResourceUpdate: () => $TSAny; diff --git a/packages/amplify-cli/bin/amplify b/packages/amplify-cli/bin/amplify index 26efa203026..b56bea3c1e6 100755 --- a/packages/amplify-cli/bin/amplify +++ b/packages/amplify-cli/bin/amplify @@ -1,4 +1,4 @@ -#!/usr/bin/env node --inspect-brk +#!/usr/bin/env node if (process.pkg) { require('../lib/utils/copy-override').copyOverride(); } else { diff --git a/packages/amplify-cli/package.json b/packages/amplify-cli/package.json index 78adf36d944..2391bd48d4f 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -112,7 +112,7 @@ "@types/promise-sequential": "^1.1.0", "@types/tar-fs": "^2.0.0", "@types/update-notifier": "^4.1.0", - "amplify-function-plugin-interface": "1.4.1", + "amplify-function-plugin-interface": "1.6.0", "nock": "^12.0.3" }, "jest": { diff --git a/packages/amplify-cli/src/context-extensions.ts b/packages/amplify-cli/src/context-extensions.ts index 6745df2fe5b..fb43f080b05 100644 --- a/packages/amplify-cli/src/context-extensions.ts +++ b/packages/amplify-cli/src/context-extensions.ts @@ -312,7 +312,7 @@ const CLI_TABLE_MARKDOWN = { function attachTemplate(context: Context) { context.template = { - async generate(opts: any): Promise { + async generate(opts: { template: string; target: string; props: object; directory: string }): Promise { const ejs = require('ejs'); const template = opts.template; const target = opts.target; diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/copy-batch.ts b/packages/amplify-cli/src/extensions/amplify-helpers/copy-batch.ts index 826feed3b99..0f469fbca78 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/copy-batch.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/copy-batch.ts @@ -1,4 +1,4 @@ -import { JSONUtilities, $TSAny, $TSContext, $TSObject } from 'amplify-cli-core'; +import { $TSContext, $TSCopyJob, JSONUtilities } from 'amplify-cli-core'; /** * Runs a series of jobs through the templating system. @@ -7,15 +7,15 @@ import { JSONUtilities, $TSAny, $TSContext, $TSObject } from 'amplify-cli-core'; * @param {$TSAny[]} jobs - A list of jobs to run. * @param {$TSAny} props - The props to use for variable replacement. * @param {boolean} force - Force CF template generation - * @param {$TSAny[]|$TSObject} writeParams - data for the CF template but not parameters file + * @param {$TSAny[]|$TSObject} writeParams - boolean of whether props should be written to job.paramsFile, or an object that should be written to job.paramsFile */ -export async function copyBatch(context: $TSContext, jobs: $TSAny, props: $TSAny, force: boolean, writeParams?: $TSAny[] | $TSObject) { +export async function copyBatch(context: $TSContext, jobs: $TSCopyJob, props: object, force?: boolean, writeParams?: boolean | object) { // grab some features - const { template, prompt, filesystem } = context as $TSAny; + const { template, prompt, filesystem } = context; const { confirm } = prompt; // If the file exists - const shouldGenerate = async (target: $TSAny, force: boolean) => { + const shouldGenerate = async (target: string, force?: boolean) => { if (!filesystem.exists(target) || force) return true; return confirm(`overwrite ${target}`); }; diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts b/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts index ee96351f1e7..1e21d81df4a 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts @@ -1,17 +1,10 @@ import * as path from 'path'; -<<<<<<< HEAD -import { JSONUtilities, pathManager, $TSAny, $TSContext } from 'amplify-cli-core'; - -export function readBreadcrumbs(context: $TSContext, category: string, resourceName: string): $TSAny { - const breadcrumbsPath = path.join(pathManager.getBackendDirPath(), category, resourceName, context.amplify.constants.BreadcrumbsFileName); -======= import { JSONUtilities, pathManager } from 'amplify-cli-core'; import { amplifyCLIConstants } from './constants'; import { leaveBreadcrumbs } from './leave-breadcrumbs'; export function readBreadcrumbs(category: string, resourceName: string) { const breadcrumbsPath = path.join(pathManager.getBackendDirPath(), category, resourceName, amplifyCLIConstants.BreadcrumbsFileName); ->>>>>>> chore: separate func invoke and build let breadcrumbs = JSONUtilities.readJson(breadcrumbsPath, { throwIfNotExist: false, }); diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/update-amplify-meta.ts b/packages/amplify-cli/src/extensions/amplify-helpers/update-amplify-meta.ts index 9ee58468c52..2c9e2a7664f 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/update-amplify-meta.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/update-amplify-meta.ts @@ -7,6 +7,7 @@ import { JSONUtilities, pathManager, stateManager, $TSAny, $TSMeta, $TSObject, R import { ServiceName } from 'amplify-category-function'; import _ from 'lodash'; import { BuildType } from 'amplify-function-plugin-interface'; +import { buildTypeKeyMap } from 'amplify-category-function'; export function updateAwsMetaFile( filePath: string, @@ -191,11 +192,6 @@ export function updateamplifyMetaAfterBuild({ category, resourceName }: Resource stateManager.setMeta(undefined, amplifyMeta); } -const buildTypeKeyMap: Record = { - [BuildType.PROD]: 'lastBuildTimeStamp', - [BuildType.DEV]: 'lastDevBuildTimeStamp', -}; - export function updateAmplifyMetaAfterPackage({ category, resourceName }: ResourceTuple, zipFilename: string) { const amplifyMeta = stateManager.getMeta(); _.set(amplifyMeta, [category, resourceName, 'lastPackageTimeStamp'], new Date()); diff --git a/packages/amplify-function-plugin-interface/src/index.ts b/packages/amplify-function-plugin-interface/src/index.ts index 85407e2c390..3d971fdc7cf 100644 --- a/packages/amplify-function-plugin-interface/src/index.ts +++ b/packages/amplify-function-plugin-interface/src/index.ts @@ -1,11 +1,10 @@ -import { $TSContext } from 'amplify-cli-core'; /* Function Runtime Contributor Types */ // All Function Runtime Contributor plugins must export a function of this type named 'functionRuntimeContributorFactory' export type FunctionRuntimeContributorFactory = ( - context: $TSContext, + context: any, ) => Contributor & FunctionRuntimeLifecycleManager; // Subset of FunctionParameters that defines the function runtime @@ -74,8 +73,8 @@ export type BuildRequest = { }; export enum BuildType { - PROD, - DEV, + PROD = 'PROD', + DEV = 'DEV', } // Request sent to package a function diff --git a/packages/amplify-util-mock/package.json b/packages/amplify-util-mock/package.json index 8f1b9ab3a83..ec7cc9a9fe9 100644 --- a/packages/amplify-util-mock/package.json +++ b/packages/amplify-util-mock/package.json @@ -45,6 +45,7 @@ "@types/semver": "^7.1.0", "@types/which": "^1.3.2", "amplify-cli-core":"^1.14.1", + "amplify-function-plugin-interface": "1.6.0", "aws-appsync": "^2.0.2", "aws-sdk": "^2.765.0", "aws-sdk-mock": "^5.1.0", diff --git a/packages/amplify-util-mock/src/func/index.ts b/packages/amplify-util-mock/src/func/index.ts index a40b74d9350..fc61bd9d738 100644 --- a/packages/amplify-util-mock/src/func/index.ts +++ b/packages/amplify-util-mock/src/func/index.ts @@ -1,67 +1,53 @@ -import { JSONUtilities, pathManager, $TSContext } from 'amplify-cli-core'; -import { getInvoker, category, isMockable } from 'amplify-category-function'; +import { getInvoker, category, isMockable, getBuilder } from 'amplify-category-function'; import * as path from 'path'; import * as inquirer from 'inquirer'; import { loadMinimalLambdaConfig } from '../utils/lambda/loadMinimal'; import { hydrateAllEnvVars } from '../utils'; +import { $TSContext, JSONUtilities, pathManager, stateManager } from 'amplify-cli-core'; +import _ = require('lodash'); +import { BuildType } from 'amplify-function-plugin-interface'; const DEFAULT_TIMEOUT_SECONDS = 10; -export async function start(context: $TSContext) { - if (!context.input.subCommands || context.input.subCommands.length < 1) { - throw new Error('Specify the function name to invoke with "amplify mock function "'); - } - - const resourceName = context.input.subCommands[0]; - // check that the resource is mockable - const mockable = isMockable(context, resourceName); - if (!mockable.isMockable) { - throw new Error(`Unable to mock ${resourceName}. ${mockable.reason}`); - } - const { amplify } = context; - const resourcePath = path.join(pathManager.getBackendDirPath(), category, resourceName); - const eventNameValidator = amplify.inputValidation({ - operator: 'regex', - value: '^[a-zA-Z0-9/._-]+?\\.json$', - onErrorMsg: 'Provide a valid unix-like path to a .json file', - required: true, - }); - let eventName: string = context.input.options ? context.input.options.event : undefined; - let promptForEvent = true; - if (eventName) { - const validatorOutput = eventNameValidator(eventName); - const isValid = typeof validatorOutput !== 'string'; - if (!isValid) { - context.print.warning(validatorOutput); +export async function start(context) { + const ampMeta = stateManager.getMeta(); + let resourceName = context?.input?.subCommands?.[0]; + if (!resourceName) { + const choices = _.keys(_.get(ampMeta, ['function'])).filter(resourceName => isMockable(context, resourceName).isMockable); + if (choices.length < 1) { + throw new Error('There are no mockable functions in the project. Use `amplify add function` to create one.'); + } else if (choices.length == 1) { + resourceName = choices[0]; } else { - promptForEvent = false; + const resourceNameQuestion = [ + { + type: 'list', + name: 'resourceName', + message: 'Select the function to mock', + choices, + }, + ]; + ({ resourceName } = await inquirer.prompt<{ resourceName: string }>(resourceNameQuestion)); + } + } else { + const mockable = isMockable(context, resourceName); + if (!mockable.isMockable) { + throw new Error(`Unable to mock ${resourceName}. ${mockable.reason}`); } } - if (promptForEvent) { - const resourceQuestions = [ - { - type: 'input', - name: 'eventName', - message: `Provide the path to the event JSON object relative to ${resourcePath}`, - validate: eventNameValidator, - default: 'src/event.json', - }, - ]; - const resourceAnswers = await inquirer.prompt(resourceQuestions); - eventName = resourceAnswers.eventName as string; - } - - const event = JSONUtilities.readJson(path.resolve(path.join(resourcePath, eventName))); const lambdaConfig = loadMinimalLambdaConfig(resourceName, { env: context.amplify.getEnvInfo().envName }); if (!lambdaConfig || !lambdaConfig.handler) { throw new Error(`Could not parse handler for ${resourceName} from cloudformation file`); } const { allResources } = await context.amplify.getResourceStatus(); + const event = await resolveEvent(context, resourceName); const envVars = hydrateAllEnvVars(allResources, lambdaConfig.environment); + context.print.warning('Ensuring latest function changes are built...'); + await getBuilder(context, resourceName, BuildType.DEV)(); const invoker = await getInvoker(context, { resourceName, handler: lambdaConfig.handler, envVars }); - context.print.success('Starting execution...'); + context.print.warning('Starting execution...'); await timeConstrainedInvoker(invoker({ event }), context.input.options) .then(result => { const msg = typeof result === 'object' ? JSON.stringify(result) : result; @@ -90,3 +76,41 @@ const getTimer = (options: { timeout?: string }) => { https://aws.amazon.com/about-aws/whats-new/2018/10/aws-lambda-supports-functions-that-can-run-up-to-15-minutes/\n`; return new Promise((_, reject) => setTimeout(() => reject(new Error(timeoutErrorMessage)), lambdaTimeoutSeconds * 1000)); }; + +const resolveEvent = async (context: $TSContext, resourceName: string): Promise => { + const { amplify } = context; + const resourcePath = path.join(pathManager.getBackendDirPath(), category, resourceName); + const eventNameValidator = amplify.inputValidation({ + operator: 'regex', + value: '^[a-zA-Z0-9/._-]+?\\.json$', + onErrorMsg: 'Provide a valid unix-like path to a .json file', + required: true, + }); + let eventName: string = context.input.options ? context.input.options.event : undefined; + let promptForEvent = true; + if (eventName) { + const validatorOutput = eventNameValidator(eventName); + const isValid = typeof validatorOutput !== 'string'; + if (!isValid) { + context.print.warning(validatorOutput); + } else { + promptForEvent = false; + } + } + + if (promptForEvent) { + const eventNameQuestion = [ + { + type: 'input', + name: 'eventName', + message: `Provide the path to the event JSON object relative to ${resourcePath}`, + validate: eventNameValidator, + default: 'src/event.json', + }, + ]; + const resourceAnswers = await inquirer.prompt(eventNameQuestion); + eventName = resourceAnswers.eventName as string; + } + + return JSONUtilities.readJson(path.resolve(path.join(resourcePath, eventName))); +};