From 33a1a022b24caf2a4a516438180fb9c731a8b47e Mon Sep 17 00:00:00 2001 From: Edward Foyle Date: Mon, 25 Jan 2021 09:47:42 -0800 Subject: [PATCH] chore: separate func invoke and build --- .../src/commands/function/build.ts | 53 ++++++++--- .../amplify-category-function/src/index.ts | 39 ++++----- .../provider-utils/awscloudformation/index.ts | 2 +- .../lambda-walkthrough.ts | 2 +- .../types/packaging-types.ts | 12 +++ .../awscloudformation/utils/buildFunction.ts | 47 ++++++++++ .../utils/functionPluginLoader.ts | 14 +++ .../awscloudformation/utils/package.ts | 19 ++++ .../utils/packageFunction.ts | 36 ++++++++ .../awscloudformation/utils/packageLayer.ts | 26 ++---- packages/amplify-cli-core/package.json | 1 + packages/amplify-cli-core/src/index.ts | 21 +++-- packages/amplify-cli/bin/amplify | 2 +- packages/amplify-cli/package.json | 1 + .../amplify-cli/src/domain/amplify-toolkit.ts | 5 -- .../amplify-helpers/build-resources.ts | 32 ------- .../amplify-helpers/leave-breadcrumbs.ts | 7 +- .../amplify-helpers/load-runtime-plugin.ts | 4 +- .../amplify-helpers/read-breadcrumbs.ts | 11 ++- .../amplify-helpers/update-amplify-meta.ts | 29 +++---- .../src/utils/build.ts | 39 ++++----- .../src/utils/invoke.ts | 12 --- .../package.json | 3 + .../src/index.ts | 18 ++-- .../src/index.ts | 2 +- .../src/localinvoke.ts | 17 +--- .../src/runtime.ts | 56 +++++------- .../src/index.ts | 2 +- .../src/utils/build.ts | 8 +- .../src/utils/invoke.ts | 8 -- .../src/utils/package.ts | 2 +- .../src/__tests__/utils/legacyBuild.test.ts | 2 +- .../src/index.ts | 20 ++--- .../src/utils/legacyBuild.ts | 8 +- .../src/utils/legacyPackage.ts | 2 +- .../src/build-resources.js | 87 ------------------- .../src/index.ts | 6 -- .../src/push-resources.ts | 43 ++++----- .../src/index.ts | 10 +-- .../src/util/buildUtils.ts | 12 +-- .../src/util/packageUtils.ts | 2 +- 41 files changed, 351 insertions(+), 371 deletions(-) create mode 100644 packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts create mode 100644 packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts create mode 100644 packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts create mode 100644 packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageFunction.ts delete mode 100644 packages/amplify-cli/src/extensions/amplify-helpers/build-resources.ts delete mode 100644 packages/amplify-provider-awscloudformation/src/build-resources.js diff --git a/packages/amplify-category-function/src/commands/function/build.ts b/packages/amplify-category-function/src/commands/function/build.ts index 7ccb8ec3371..36edf0f76c5 100644 --- a/packages/amplify-category-function/src/commands/function/build.ts +++ b/packages/amplify-category-function/src/commands/function/build.ts @@ -1,18 +1,43 @@ -import { category as categoryName } from '../../constants'; +import { $TSContext } from 'amplify-cli-core'; +import { ServiceName } from '../..'; +import { category } from '../../constants'; +import { ResourceMeta } from '../../provider-utils/awscloudformation/types/packaging-types'; +import { buildFunction } from '../../provider-utils/awscloudformation/utils/buildFunction'; +import { packageResource } from '../../provider-utils/awscloudformation/utils/package'; -const subcommand = 'build'; +export const name = 'build'; -module.exports = { - name: subcommand, - run: async context => { - const { amplify, parameters } = context; - const resourceName = parameters.first; +/** + * To maintain existing behavior, this function builds and then packages lambda functions + */ +export const run = async (context: $TSContext) => { + const resourceName = context?.input?.subCommands?.[0]; + const cont = + !!resourceName || + context.input?.options?.yes || + (await context.amplify.confirmPrompt( + 'This will build all functions and layers in your project. Are you sure you want to continue?', + false, + )); + if (!cont) { + return; + } + try { + const resourcesToBuild = (await getSelectedResources(context, resourceName)) + .filter(resource => resource.build) + .filter(resource => resource.service === ServiceName.LambdaFunction); + for await (const resource of resourcesToBuild) { + resource.lastBuildTimeStamp = await buildFunction(context, resource); + await packageResource(context, resource); + } + } catch (err) { + context.print.info(err.stack); + context.print.error('There was an error building the function resources'); + context.usageData.emitError(err); + process.exitCode = 1; + } +}; - return amplify.buildResources(context, categoryName, resourceName).catch(err => { - context.print.info(err.stack); - context.print.error('There was an error building the function resources'); - context.usageData.emitError(err); - process.exitCode = 1; - }); - }, +const getSelectedResources = async (context: $TSContext, resourceName?: string) => { + return (await context.amplify.getResourceStatus(category, resourceName)).allResources as ResourceMeta[]; }; diff --git a/packages/amplify-category-function/src/index.ts b/packages/amplify-category-function/src/index.ts index ee694a84d0b..b6945097977 100644 --- a/packages/amplify-category-function/src/index.ts +++ b/packages/amplify-category-function/src/index.ts @@ -2,12 +2,14 @@ import path from 'path'; import { category } from './constants'; export { category } from './constants'; import { FunctionBreadcrumbs, FunctionRuntimeLifecycleManager } from 'amplify-function-plugin-interface'; -import { stateManager } from 'amplify-cli-core'; +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 { packageLayer, hashLayerResource } from './provider-utils/awscloudformation/utils/packageLayer'; +export { buildFunction } 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'; @@ -148,25 +150,22 @@ export async function initEnv(context) { await sequential(functionTasks); } -// returns a function that can be used to invoke the lambda locally -export async function getInvoker(context: any, params: InvokerParameters): Promise<({ event: any }) => Promise> { - const resourcePath = path.join(context.amplify.pathManager.getBackendDirPath(), category, params.resourceName); - const breadcrumbs: FunctionBreadcrumbs = context.amplify.readBreadcrumbs(context, category, params.resourceName); - const runtimeManager: FunctionRuntimeLifecycleManager = await context.amplify.loadRuntimePlugin(context, breadcrumbs.pluginId); - - const lastBuildTimestampStr = (await context.amplify.getResourceStatus(category, params.resourceName)).allResources.find( - resource => resource.resourceName === params.resourceName, - ).lastBuildTimeStamp as string; - - return async request => - await runtimeManager.invoke({ - handler: params.handler, - event: JSON.stringify(request.event), - env: context.amplify.getEnvInfo().envName, - runtime: breadcrumbs.functionRuntime, +// Returns a wrapper around FunctionRuntimeLifecycleManager.invoke() that can be used to invoke the function with only an event +export async function getInvoker( + context: $TSContext, + { handler, resourceName, envVars }: InvokerParameters, +): Promise<({ event: unknown }) => Promise> { + const resourcePath = path.join(pathManager.getBackendDirPath(), category, resourceName); + const { pluginId, functionRuntime }: FunctionBreadcrumbs = context.amplify.readBreadcrumbs(category, resourceName); + const runtimeManager: FunctionRuntimeLifecycleManager = await context.amplify.loadRuntimePlugin(context, pluginId); + + return ({ event }) => + runtimeManager.invoke({ + handler: handler, + event: JSON.stringify(event), + runtime: functionRuntime, srcRoot: resourcePath, - envVars: params.envVars, - lastBuildTimestamp: lastBuildTimestampStr ? new Date(lastBuildTimestampStr) : undefined, + envVars: envVars, }); } diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/index.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/index.ts index db1f06446ae..614a9ad96a5 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/index.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/index.ts @@ -227,7 +227,7 @@ export async function updateFunctionResource(context, category, service, paramet } if (!parameters || (parameters && !parameters.skipEdit)) { - const breadcrumb = context.amplify.readBreadcrumbs(context, categoryName, parameters.resourceName); + const breadcrumb = context.amplify.readBreadcrumbs(categoryName, parameters.resourceName); const displayName = 'trigger' in parameters ? parameters.resourceName : undefined; await openEditor(context, category, parameters.resourceName, { defaultEditorFile: breadcrumb.defaultEditorFile }, displayName, false); } diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/service-walkthroughs/lambda-walkthrough.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/service-walkthroughs/lambda-walkthrough.ts index c078634cb55..e1d5ebb510e 100644 --- a/packages/amplify-category-function/src/provider-utils/awscloudformation/service-walkthroughs/lambda-walkthrough.ts +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/service-walkthroughs/lambda-walkthrough.ts @@ -177,7 +177,7 @@ export async function updateWalkthrough(context, lambdaToUpdate?: string) { const projectBackendDirPath = context.amplify.pathManager.getBackendDirPath(); const resourceDirPath = path.join(projectBackendDirPath, category, functionParameters.resourceName); const currentParameters = loadFunctionParameters(context, resourceDirPath); - const functionRuntime = context.amplify.readBreadcrumbs(context, category, functionParameters.resourceName).functionRuntime as string; + const functionRuntime = context.amplify.readBreadcrumbs(category, functionParameters.resourceName).functionRuntime as string; const cfnParameters: any = JSONUtilities.readJson(path.join(resourceDirPath, parametersFileName), { throwIfNotExist: false }) || {}; const scheduleParameters = { 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 new file mode 100644 index 00000000000..8359f83d2a2 --- /dev/null +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/types/packaging-types.ts @@ -0,0 +1,12 @@ +import { $TSContext, ResourceTuple } from 'amplify-cli-core'; + +export type ResourceMeta = ResourceTuple & { + service: string; + build: boolean; + distZipFilename: string; + lastBuildTimeStamp?: string; + lastPackageTimeStamp?: string; + skipHashing: boolean; +}; + +export type Packager = (context: $TSContext, resource: ResourceMeta) => 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 new file mode 100644 index 00000000000..fe50db280fc --- /dev/null +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/buildFunction.ts @@ -0,0 +1,47 @@ +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'; + +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); + + const runtimePlugin: FunctionRuntimeLifecycleManager = (await context.amplify.loadRuntimePlugin( + context, + breadcrumbs.pluginId, + )) as FunctionRuntimeLifecycleManager; + + 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}`); + } + + const prevBuildTime = resource.lastBuildTimeStamp ? new Date(resource.lastBuildTimeStamp) : undefined; + + // build the function + let rebuilt = false; + if (breadcrumbs.scripts && breadcrumbs.scripts.build) { + // TODO + throw new Error('Executing custom build scripts is not yet implemented'); + } else { + const buildRequest: BuildRequest = { + buildType, + srcRoot: resourcePath, + runtime: breadcrumbs.functionRuntime, + legacyBuildHookParams: { + projectRoot: pathManager.findProjectRoot(), + resourceName: resource.resourceName, + }, + lastBuildTimeStamp: prevBuildTime, + }; + rebuilt = (await runtimePlugin.build(buildRequest)).rebuilt; + } + if (rebuilt) { + context.amplify.updateamplifyMetaAfterBuild(resource, buildType); + return new Date().toISOString(); + } else { + return resource?.lastBuildTimeStamp; + } +}; 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 f385000d2f0..2c366ec71a8 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 @@ -13,6 +13,9 @@ 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 { category } from '../../../constants'; /* * This file contains the logic for loading, selecting and executing function plugins (currently runtime and template plugins) */ @@ -205,6 +208,17 @@ export async function loadPluginFromFactory(pluginPath, expectedFactoryFunction, return plugin[expectedFactoryFunction](context); } +export async function getRuntimeManager( + context: $TSContext, + resourceName: string, +): Promise { + const { pluginId, functionRuntime } = context.amplify.readBreadcrumbs(category, resourceName); + return { + ...((await context.amplify.loadRuntimePlugin(context, pluginId)) as FunctionRuntimeLifecycleManager), + runtime: functionRuntime, + }; +} + // Convenience interfaces that are private to this class interface PluginSelectionOptions { 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 new file mode 100644 index 00000000000..fc87d812d37 --- /dev/null +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/package.ts @@ -0,0 +1,19 @@ +import { $TSContext } from 'amplify-cli-core'; +import _ from 'lodash'; +import { Packager, ResourceMeta } 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 servicePackagerMap: Record = { + [ServiceName.LambdaFunction]: packageFunction, + [ServiceName.LambdaLayer]: packageLayer, +}; + +const getServicePackager = (service: string) => + (servicePackagerMap[service] ?? + (() => { + throw new Error(`Unknown function service type ${service}`); + })) as Packager; diff --git a/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageFunction.ts b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageFunction.ts new file mode 100644 index 00000000000..545edd43c53 --- /dev/null +++ b/packages/amplify-category-function/src/provider-utils/awscloudformation/utils/packageFunction.ts @@ -0,0 +1,36 @@ +// For legacy purposes, the method builds and packages the resource + +import { pathManager } from 'amplify-cli-core'; +import * as path from 'path'; +import * as fs from 'fs-extra'; +import { Packager } from '../types/packaging-types'; +import { getRuntimeManager } from './functionPluginLoader'; + +/** + * Packages lambda source code and artifacts into a lambda-compatible .zip file + */ +export const packageFunction: Packager = async (context, resource) => { + const resourcePath = path.join(pathManager.getBackendDirPath(), resource.category, resource.resourceName); + const runtimeManager = await getRuntimeManager(context, resource.resourceName); + const distDir = path.join(resourcePath, 'dist'); + if (!fs.existsSync(distDir)) { + fs.mkdirSync(distDir); + } + const destination = path.join(distDir, 'latest-build.zip'); + const packageRequest = { + env: context.amplify.getEnvInfo().envName, + srcRoot: resourcePath, + dstFilename: destination, + runtime: runtimeManager.runtime, + lastPackageTimeStamp: resource.lastPackageTimeStamp ? new Date(resource.lastPackageTimeStamp) : undefined, + lastBuildTimeStamp: resource.lastBuildTimeStamp ? new Date(resource.lastBuildTimeStamp) : undefined, + skipHashing: resource.skipHashing, + }; + const packageResult = await runtimeManager.package(packageRequest); + const packageHash = packageResult.packageHash; + const zipFilename = packageHash + ? `${resource.resourceName}-${packageHash}-build.zip` + : resource.distZipFilename ?? `${resource.category}-${resource.resourceName}-build.zip`; + context.amplify.updateAmplifyMetaAfterPackage(resource, zipFilename); + return { zipFilename, zipFilePath: destination }; +}; 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 c4522ab8121..63c4ba8463f 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 @@ -5,8 +5,7 @@ import { prompt } from 'inquirer'; import path from 'path'; import _ from 'lodash'; import { hashElement } from 'folder-hash'; -import { pathManager } from 'amplify-cli-core'; -import { FunctionDependency } from 'amplify-function-plugin-interface'; +import { $TSContext, pathManager } from 'amplify-cli-core'; import { ServiceName, provider } from './constants'; import { previousPermissionsQuestion } from './layerHelpers'; import { @@ -22,13 +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'; -export async function packageLayer(context, resource: Resource) { +export const packageLayer: Packager = async (context, resource) => { await ensureLayerVersion(context, resource.resourceName); return zipLayer(context, resource); -} +}; -async function zipLayer(context, resource: Resource) { +async function zipLayer(context: $TSContext, resource: ResourceMeta) { const zipFilename = 'latest-build.zip'; const layerName = resource.resourceName; const layerDirPath = path.join(pathManager.getBackendDirPath(), resource.category, layerName); @@ -38,7 +38,7 @@ async function zipLayer(context, resource: Resource) { const zip = archiver.create('zip'); const output = fs.createWriteStream(destination); - return new Promise((resolve, reject) => { + return new Promise<{ zipFilePath: string; zipFilename: string }>((resolve, reject) => { output.on('close', () => { // check zip size is less than 250MB if (validFilesize(destination)) { @@ -86,7 +86,7 @@ async function zipLayer(context, resource: Resource) { } // Check hash results for content changes, bump version if so -async function ensureLayerVersion(context: any, layerName: string) { +async function ensureLayerVersion(context: $TSContext, layerName: string) { const layerState = getLayerMetadataFactory(context)(layerName); const isNewVersion = await layerState.syncVersions(); const latestVersion = layerState.getLatestVersion(); @@ -165,10 +165,7 @@ export const hashLayerVersionContents = async (layerPath: string): Promise; +// There are tons of places where we use these two pieces of information to identify a resource +// We can use this type to type those instances +export interface ResourceTuple { + category: string; + resourceName: string; +} + export enum AmplifyFrontend { android = 'android', ios = 'ios', @@ -119,8 +127,7 @@ export interface AmplifyProjectConfig { // Temporary interface until Context refactor interface AmplifyToolkit { - buildResources: () => $TSAny; - confirmPrompt: (prompt: string, defaultValue?: boolean) => $TSAny; + 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; @@ -189,8 +196,8 @@ interface AmplifyToolkit { updateamplifyMetaAfterResourceDelete: (category: string, resourceName: string) => void; updateProvideramplifyMeta: (providerName: string, options: $TSObject) => void; updateamplifyMetaAfterPush: (resources: $TSObject[]) => void; - updateamplifyMetaAfterBuild: (resource: $TSObject) => void; - updateAmplifyMetaAfterPackage: (resource: $TSObject, zipFilename: string) => void; + updateamplifyMetaAfterBuild: (resource: ResourceTuple, buildType?: BuildType) => void; + updateAmplifyMetaAfterPackage: (resource: ResourceTuple, zipFilename: string) => void; updateBackendConfigAfterResourceAdd: (category: string, resourceName: string, resourceData: $TSAny) => $TSAny; updateBackendConfigAfterResourceUpdate: () => $TSAny; updateBackendConfigAfterResourceRemove: () => $TSAny; @@ -212,9 +219,9 @@ interface AmplifyToolkit { forceRemoveResource: () => $TSAny; writeObjectAsJson: () => $TSAny; hashDir: () => $TSAny; - leaveBreadcrumbs: (context: $TSContext, category: string, resourceName: string, breadcrumbs: $TSAny) => void; - readBreadcrumbs: (context: $TSContext, category: string, resourceName: string) => $TSAny; - loadRuntimePlugin: () => $TSAny; + leaveBreadcrumbs: (category: string, resourceName: string, breadcrumbs: unknown) => void; + readBreadcrumbs: (category: string, resourceName: string) => $TSAny; + loadRuntimePlugin: (context: $TSContext, pluginId: string) => Promise<$TSAny>; getImportedAuthProperties: ( context: $TSContext, ) => { diff --git a/packages/amplify-cli/bin/amplify b/packages/amplify-cli/bin/amplify index b56bea3c1e6..26efa203026 100755 --- a/packages/amplify-cli/bin/amplify +++ b/packages/amplify-cli/bin/amplify @@ -1,4 +1,4 @@ -#!/usr/bin/env node +#!/usr/bin/env node --inspect-brk 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 e08d1f37592..78adf36d944 100644 --- a/packages/amplify-cli/package.json +++ b/packages/amplify-cli/package.json @@ -112,6 +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", "nock": "^12.0.3" }, "jest": { diff --git a/packages/amplify-cli/src/domain/amplify-toolkit.ts b/packages/amplify-cli/src/domain/amplify-toolkit.ts index 3df898c3734..251780d4858 100644 --- a/packages/amplify-cli/src/domain/amplify-toolkit.ts +++ b/packages/amplify-cli/src/domain/amplify-toolkit.ts @@ -3,7 +3,6 @@ import { $TSAny, $TSContext } from 'amplify-cli-core'; import { Context } from './context'; export class AmplifyToolkit { - private _buildResources: any; private _confirmPrompt: any; private _constants: any; private _constructExeInfo: any; @@ -90,10 +89,6 @@ export class AmplifyToolkit { private _amplifyHelpersDirPath: string = path.normalize(path.join(__dirname, '../extensions/amplify-helpers')); - get buildResources(): any { - this._buildResources = this._buildResources || require(path.join(this._amplifyHelpersDirPath, 'build-resources')).buildResources; - return this._buildResources; - } get confirmPrompt(): any { this._confirmPrompt = this._confirmPrompt || require(path.join(this._amplifyHelpersDirPath, 'confirm-prompt')).confirmPrompt; return this._confirmPrompt; diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/build-resources.ts b/packages/amplify-cli/src/extensions/amplify-helpers/build-resources.ts deleted file mode 100644 index cac84025296..00000000000 --- a/packages/amplify-cli/src/extensions/amplify-helpers/build-resources.ts +++ /dev/null @@ -1,32 +0,0 @@ -import ora from 'ora'; -import { getProviderPlugins } from './get-provider-plugins'; - -const spinner = ora('Building resources. This may take a few minutes...'); - -export function buildResources(context, category, resourceName) { - const confirmationPromise = - context.input.options && context.input.options.yes - ? Promise.resolve(true) - : context.amplify.confirmPrompt('Are you sure you want to continue building the resources?'); - - return confirmationPromise - .then(answer => { - if (answer) { - const providerPlugins = getProviderPlugins(context); - const providerPromises: (() => Promise)[] = []; - - Object.keys(providerPlugins).forEach(provider => { - const pluginModule = require(providerPlugins[provider]); - providerPromises.push(pluginModule.buildResources(context, category, resourceName)); - }); - - spinner.start(); - return Promise.all(providerPromises); - } - }) - .then(() => spinner.succeed('All resources are built.')) - .catch(err => { - spinner.fail('An error occurred when building the resources.'); - throw err; - }); -} diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/leave-breadcrumbs.ts b/packages/amplify-cli/src/extensions/amplify-helpers/leave-breadcrumbs.ts index d7ae50a467f..8c8964e210c 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/leave-breadcrumbs.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/leave-breadcrumbs.ts @@ -1,7 +1,8 @@ -import { JSONUtilities, pathManager, $TSAny, $TSContext } from 'amplify-cli-core'; +import { JSONUtilities, pathManager } from 'amplify-cli-core'; import * as path from 'path'; +import { amplifyCLIConstants } from './constants'; -export function leaveBreadcrumbs(context: $TSContext, category: string, resourceName: string, breadcrumbs: $TSAny) { - const destPath = path.join(pathManager.getBackendDirPath(), category, resourceName, context.amplify.constants.BreadcrumbsFileName); +export function leaveBreadcrumbs(category: string, resourceName: string, breadcrumbs: unknown) { + const destPath = path.join(pathManager.getBackendDirPath(), category, resourceName, amplifyCLIConstants.BreadcrumbsFileName); JSONUtilities.writeJson(destPath, breadcrumbs); } diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/load-runtime-plugin.ts b/packages/amplify-cli/src/extensions/amplify-helpers/load-runtime-plugin.ts index 8f0a42712b9..dbfc7ee53e2 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/load-runtime-plugin.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/load-runtime-plugin.ts @@ -1,4 +1,6 @@ -export async function loadRuntimePlugin(context, pluginId) { +import { $TSContext } from 'amplify-cli-core'; + +export async function loadRuntimePlugin(context: $TSContext, pluginId: string) { if (!context.pluginPlatform.plugins.functionRuntime) { throw new Error('No function runtime plugins found. Try "amplify plugin scan" and then rerun the command.'); } 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 5afe864baf6..ee96351f1e7 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/read-breadcrumbs.ts @@ -1,8 +1,17 @@ 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, }); @@ -15,7 +24,7 @@ export function readBreadcrumbs(context: $TSContext, category: string, resourceN useLegacyBuild: true, }; - context.amplify.leaveBreadcrumbs(context, category, resourceName, breadcrumbs); + leaveBreadcrumbs(category, resourceName, breadcrumbs); } return breadcrumbs; } 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 fab3308d49b..9ee58468c52 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 @@ -3,8 +3,10 @@ import * as path from 'path'; import { hashElement } from 'folder-hash'; import glob from 'glob'; import { updateBackendConfigAfterResourceAdd, updateBackendConfigAfterResourceUpdate } from './update-backend-config'; -import { JSONUtilities, pathManager, stateManager, $TSAny, $TSMeta, $TSObject } from 'amplify-cli-core'; +import { JSONUtilities, pathManager, stateManager, $TSAny, $TSMeta, $TSObject, ResourceTuple } from 'amplify-cli-core'; import { ServiceName } from 'amplify-category-function'; +import _ from 'lodash'; +import { BuildType } from 'amplify-function-plugin-interface'; export function updateAwsMetaFile( filePath: string, @@ -183,26 +185,21 @@ function getHashForResourceDir(dirPath) { return hashElement(dirPath, options).then(result => result.hash); } -export function updateamplifyMetaAfterBuild(resource: $TSObject) { +export function updateamplifyMetaAfterBuild({ category, resourceName }: ResourceTuple, buildType: BuildType = BuildType.PROD) { const amplifyMeta = stateManager.getMeta(); - const currentTimestamp = new Date(); - - /*eslint-disable */ - amplifyMeta[resource.category][resource.resourceName].lastBuildTimeStamp = currentTimestamp; - /* eslint-enable */ - + _.set(amplifyMeta, [category, resourceName, buildTypeKeyMap[buildType]], new Date()); stateManager.setMeta(undefined, amplifyMeta); } -export function updateAmplifyMetaAfterPackage(resource: $TSObject, zipFilename: string) { - const amplifyMeta = stateManager.getMeta(); - const currentTimestamp = new Date(); - - /*eslint-disable */ - amplifyMeta[resource.category][resource.resourceName].lastPackageTimeStamp = currentTimestamp; - amplifyMeta[resource.category][resource.resourceName].distZipFilename = zipFilename; - /* eslint-enable */ +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()); + _.set(amplifyMeta, [category, resourceName, 'distZipFilename'], zipFilename); stateManager.setMeta(undefined, amplifyMeta); } diff --git a/packages/amplify-dotnet-function-runtime-provider/src/utils/build.ts b/packages/amplify-dotnet-function-runtime-provider/src/utils/build.ts index ba9c3ca3cd8..0856f8dff60 100644 --- a/packages/amplify-dotnet-function-runtime-provider/src/utils/build.ts +++ b/packages/amplify-dotnet-function-runtime-provider/src/utils/build.ts @@ -2,39 +2,30 @@ import path from 'path'; import fs from 'fs-extra'; import glob from 'glob'; import * as execa from 'execa'; -import { BuildRequest, BuildResult } from 'amplify-function-plugin-interface'; +import { BuildRequest, BuildResult, BuildType } from 'amplify-function-plugin-interface'; import { executableName } from '../constants'; -export const build = async (request: BuildRequest): Promise => { - return buildCore(request, 'Release'); -}; - -export const buildCore = async (request: BuildRequest, configuration: 'Release' | 'Debug'): Promise => { - const distPath = path.join(request.srcRoot, 'dist'); - const sourceFolder = path.join(request.srcRoot, 'src'); - if ( - configuration === 'Debug' || // Always refresh for mock builds - !request.lastBuildTimestamp || - !fs.existsSync(distPath) || - isBuildStale(sourceFolder, request.lastBuildTimestamp) - ) { +export const build = async ({ srcRoot, lastBuildTimeStamp, buildType }: BuildRequest): Promise => { + const distPath = path.join(srcRoot, 'dist'); + const sourceFolder = path.join(srcRoot, 'src'); + if (!lastBuildTimeStamp || !fs.existsSync(distPath) || isBuildStale(sourceFolder, lastBuildTimeStamp)) { if (!fs.existsSync(distPath)) { fs.mkdirSync(distPath); } const buildArguments = []; - switch (configuration) { - case 'Release': - buildArguments.push('publish', '-c', configuration, '-o', distPath); + switch (buildType) { + case BuildType.PROD: + buildArguments.push('publish', '-c', 'Release', '-o', distPath); break; - case 'Debug': + case BuildType.DEV: // Debug config, copy all required assemblies for mocking. // The CopyLocalLockFileAssemblies really shouldn't be necessary, but // we encountered CircleCI e2e test issues without it. - buildArguments.push('build', '-c', configuration, '-p:CopyLocalLockFileAssemblies=true'); + buildArguments.push('build', '-c', 'Debug', '-p:CopyLocalLockFileAssemblies=true'); break; default: - throw new Error(`Unexpected configuration '${configuration}'`); + throw new Error(`Unexpected buildType: [${buildType}]`); } const result = execa.sync(executableName, buildArguments, { cwd: sourceFolder, @@ -50,21 +41,21 @@ export const buildCore = async (request: BuildRequest, configuration: 'Release' return { rebuilt: false }; }; -const isBuildStale = (sourceFolder: string, lastBuildTimestamp: Date) => { +const isBuildStale = (sourceFolder: string, lastBuildTimeStamp: Date) => { // Guard against invalid timestamp - if (!(lastBuildTimestamp instanceof Date && !isNaN((lastBuildTimestamp)))) { + if (!(lastBuildTimeStamp instanceof Date && !isNaN((lastBuildTimeStamp)))) { return true; } const dirTime = new Date(fs.statSync(sourceFolder).mtime); - if (dirTime > lastBuildTimestamp) { + if (dirTime > lastBuildTimeStamp) { return true; } const fileUpdatedAfterLastBuild = glob .sync('**/*', { cwd: sourceFolder, ignore: ['bin', 'obj', '+(bin|obj)/**/*'] }) - .find(file => new Date(fs.statSync(path.join(sourceFolder, file)).mtime) > lastBuildTimestamp); + .find(file => new Date(fs.statSync(path.join(sourceFolder, file)).mtime) > lastBuildTimeStamp); return !!fileUpdatedAfterLastBuild; }; diff --git a/packages/amplify-dotnet-function-runtime-provider/src/utils/invoke.ts b/packages/amplify-dotnet-function-runtime-provider/src/utils/invoke.ts index 0c49b742ba3..1379022901f 100644 --- a/packages/amplify-dotnet-function-runtime-provider/src/utils/invoke.ts +++ b/packages/amplify-dotnet-function-runtime-provider/src/utils/invoke.ts @@ -1,22 +1,10 @@ import path from 'path'; import fs from 'fs-extra'; -import glob from 'glob'; import * as execa from 'execa'; import { InvocationRequest } from 'amplify-function-plugin-interface'; -import { buildCore } from './build'; import { executableName } from '../constants'; export const invoke = async (request: InvocationRequest): Promise => { - await buildCore( - { - env: request.env, - runtime: request.runtime, - srcRoot: request.srcRoot, - lastBuildTimestamp: request.lastBuildTimestamp, - }, - 'Debug', - ); - const sourcePath = path.join(request.srcRoot, 'src'); let result: execa.ExecaSyncReturnValue; let tempDir: string = ''; diff --git a/packages/amplify-function-plugin-interface/package.json b/packages/amplify-function-plugin-interface/package.json index 45814eb11c9..9c9748a67d1 100644 --- a/packages/amplify-function-plugin-interface/package.json +++ b/packages/amplify-function-plugin-interface/package.json @@ -15,5 +15,8 @@ "build": "tsc", "clean": "rimraf ./lib", "watch": "tsc -w" + }, + "devDependencies": { + "amplify-cli-core":"^1.14.1" } } diff --git a/packages/amplify-function-plugin-interface/src/index.ts b/packages/amplify-function-plugin-interface/src/index.ts index ce99dcf3913..85407e2c390 100644 --- a/packages/amplify-function-plugin-interface/src/index.ts +++ b/packages/amplify-function-plugin-interface/src/index.ts @@ -1,10 +1,11 @@ +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: any, + context: $TSContext, ) => Contributor & FunctionRuntimeLifecycleManager; // Subset of FunctionParameters that defines the function runtime @@ -54,34 +55,37 @@ export type RuntimeContributionRequest = { // Request sent to invoke a function export type InvocationRequest = { srcRoot: string; - env: string; runtime: string; handler: string; event: string; - lastBuildTimestamp?: Date; envVars?: { [key: string]: string }; }; // Request sent to build a function export type BuildRequest = { - env: string; + buildType: BuildType; srcRoot: string; runtime: string; legacyBuildHookParams?: { projectRoot: string; resourceName: string; }; - lastBuildTimestamp?: Date; + lastBuildTimeStamp?: Date; }; +export enum BuildType { + PROD, + DEV, +} + // Request sent to package a function export type PackageRequest = { env: string; srcRoot: string; dstFilename: string; runtime: string; - lastBuildTimestamp: Date; - lastPackageTimestamp?: Date; + lastBuildTimeStamp: Date; + lastPackageTimeStamp?: Date; skipHashing?: boolean; }; diff --git a/packages/amplify-go-function-runtime-provider/src/index.ts b/packages/amplify-go-function-runtime-provider/src/index.ts index dbba365ee7d..ba5c5d29c72 100644 --- a/packages/amplify-go-function-runtime-provider/src/index.ts +++ b/packages/amplify-go-function-runtime-provider/src/index.ts @@ -22,7 +22,7 @@ export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactor }, checkDependencies: runtimeValue => checkDependencies(runtimeValue), package: request => packageResource(request, context), - build: request => buildResource(request, context), + build: buildResource, invoke: request => localInvoke(request, context), }; }; diff --git a/packages/amplify-go-function-runtime-provider/src/localinvoke.ts b/packages/amplify-go-function-runtime-provider/src/localinvoke.ts index 3a20e598d6b..372565263fe 100644 --- a/packages/amplify-go-function-runtime-provider/src/localinvoke.ts +++ b/packages/amplify-go-function-runtime-provider/src/localinvoke.ts @@ -1,10 +1,10 @@ import path from 'path'; import fs from 'fs-extra'; import portfinder from 'portfinder'; -import { pathManager } from 'amplify-cli-core'; +import { $TSContext, pathManager } from 'amplify-cli-core'; -import { InvocationRequest, BuildRequest } from 'amplify-function-plugin-interface'; -import { buildResourceInternal, executeCommand } from './runtime'; +import { InvocationRequest } from 'amplify-function-plugin-interface'; +import { executeCommand } from './runtime'; import { MAIN_SOURCE, MAX_PORT, BASE_PORT, BIN_LOCAL, MAIN_BINARY, MAIN_BINARY_WIN, packageName, relativeShimSrcPath } from './constants'; import execa, { ExecaChildProcess } from 'execa'; @@ -60,18 +60,9 @@ const stopLambda = async (lambdaProcess: ExecaChildProcess) => { } }; -export const localInvoke = async (request: InvocationRequest, context: any) => { +export const localInvoke = async (request: InvocationRequest, context: $TSContext) => { const localInvoker = await buildLocalInvoker(context); - // Make sure that local invocation works with the latest source we issue a build request for the function resource - const buildRequest: BuildRequest = { - env: request.env, - srcRoot: request.srcRoot, - runtime: request.runtime, - }; - - const buildResult = await buildResourceInternal(buildRequest, context, false, true); - // Find a free tcp port for the Lambda to launch on const portNumber = await portfinder.getPortPromise({ startPort: BASE_PORT, diff --git a/packages/amplify-go-function-runtime-provider/src/runtime.ts b/packages/amplify-go-function-runtime-provider/src/runtime.ts index d4562d134f4..bd23e3e49f2 100644 --- a/packages/amplify-go-function-runtime-provider/src/runtime.ts +++ b/packages/amplify-go-function-runtime-provider/src/runtime.ts @@ -1,4 +1,11 @@ -import { CheckDependenciesResult, PackageRequest, PackageResult, BuildRequest, BuildResult } from 'amplify-function-plugin-interface/src'; +import { + CheckDependenciesResult, + PackageRequest, + PackageResult, + BuildRequest, + BuildResult, + BuildType, +} from 'amplify-function-plugin-interface/src'; import * as which from 'which'; import execa from 'execa'; import archiver from 'archiver'; @@ -35,7 +42,7 @@ export const executeCommand = ( return output.stdout; }; -const isBuildStale = (resourceDir: string, lastBuildTimestamp: Date, outDir: string) => { +const isBuildStale = (resourceDir: string, lastBuildTimeStamp: Date, outDir: string) => { // If output directory does not exists or empty, rebuild required if (!fs.existsSync(outDir) || glob.sync(`${outDir}/**`).length == 0) { return true; @@ -45,45 +52,29 @@ const isBuildStale = (resourceDir: string, lastBuildTimestamp: Date, outDir: str const srcDir = path.join(resourceDir, SRC); const dirTime = new Date(fs.statSync(srcDir).mtime); - if (dirTime > lastBuildTimestamp) { + if (dirTime > lastBuildTimeStamp) { return true; } const fileUpdatedAfterLastBuild = glob .sync(`${resourceDir}/${SRC}/**`) - .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimestamp); + .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimeStamp); return !!fileUpdatedAfterLastBuild; }; -export const buildResourceInternal = async ( - request: BuildRequest, - context: any, - force: boolean, - forLocalInvoke: boolean, -): Promise => { +export const buildResource = async ({ buildType, srcRoot, lastBuildTimeStamp }: BuildRequest): Promise => { let rebuilt = false; - const buildDir = forLocalInvoke === true ? BIN_LOCAL : BIN; - const outDir = path.join(request.srcRoot, buildDir); + const buildDir = buildType === BuildType.DEV ? BIN_LOCAL : BIN; + const outDir = path.join(srcRoot, buildDir); - const isWindows = /^win/.test(process.platform); - const executableName = isWindows && forLocalInvoke ? MAIN_BINARY_WIN : MAIN_BINARY; + const isWindows = process.platform.startsWith('win'); + const executableName = isWindows && buildType ? MAIN_BINARY_WIN : MAIN_BINARY; const executablePath = path.join(outDir, executableName); - // For local invoke we've to use the build timestamp of the binary built - let timestampToCheck; - - if (forLocalInvoke === true) { - if (fs.existsSync(executablePath)) { - timestampToCheck = new Date(fs.statSync(executablePath).mtime); - } - } else { - timestampToCheck = request.lastBuildTimestamp; - } - - if (force === true || !timestampToCheck || isBuildStale(request.srcRoot, timestampToCheck, outDir)) { - const srcDir = path.join(request.srcRoot, SRC); + if (!lastBuildTimeStamp || isBuildStale(srcRoot, lastBuildTimeStamp, outDir)) { + const srcDir = path.join(srcRoot, SRC); // Clean and/or create the output directory if (fs.existsSync(outDir)) { @@ -94,7 +85,7 @@ export const buildResourceInternal = async ( const envVars: any = {}; - if (forLocalInvoke === false) { + if (buildType === BuildType.PROD) { envVars.GOOS = 'linux'; envVars.GOARCH = 'amd64'; } @@ -156,12 +147,9 @@ export const checkDependencies = async (_runtimeValue: string): Promise => - buildResourceInternal(request, context, false, false); - export const packageResource = async (request: PackageRequest, context: any): Promise => { // check if repackaging is needed - if (!request.lastPackageTimestamp || request.lastBuildTimestamp > request.lastPackageTimestamp) { + if (!request.lastPackageTimeStamp || request.lastBuildTimeStamp > request.lastPackageTimeStamp) { const packageHash = await context.amplify.hashDir(request.srcRoot, [DIST]); const zipFn = process.platform.startsWith('win') ? winZip : nixZip; await zipFn(request.srcRoot, request.dstFilename, context.print); @@ -185,13 +173,13 @@ const winZip = async (src: string, dest: string, print: any) => { print.warning('See https://github.com/aws/aws-lambda-go/issues/13#issuecomment-358729411.'); }; -const nixZip = async (src: string, dest: string) => { +const nixZip = (src: string, dest: string) => { const outDir = path.join(src, BIN); const mainFile = path.join(outDir, MAIN_BINARY); // zip source and dependencies and write to specified file const file = fs.createWriteStream(dest); - return new Promise(async (resolve, reject) => { + return new Promise((resolve, reject) => { file.on('close', () => { resolve(); }); diff --git a/packages/amplify-java-function-runtime-provider/src/index.ts b/packages/amplify-java-function-runtime-provider/src/index.ts index 7805e80d417..d7a7b815297 100644 --- a/packages/amplify-java-function-runtime-provider/src/index.ts +++ b/packages/amplify-java-function-runtime-provider/src/index.ts @@ -61,7 +61,7 @@ export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactor return result; }, package: params => packageResource(params, context), - build: params => buildResource(params), + build: buildResource, invoke: params => invokeResource(params, context), }; }; diff --git a/packages/amplify-java-function-runtime-provider/src/utils/build.ts b/packages/amplify-java-function-runtime-provider/src/utils/build.ts index 6d35b13ebe4..dfcd3d00c44 100644 --- a/packages/amplify-java-function-runtime-provider/src/utils/build.ts +++ b/packages/amplify-java-function-runtime-provider/src/utils/build.ts @@ -10,7 +10,7 @@ export const buildResource = async (request: BuildRequest): Promise const resourceDir = join(request.srcRoot); const projectPath = join(resourceDir); - if (!request.lastBuildTimestamp || isBuildStale(request.srcRoot, request.lastBuildTimestamp)) { + if (!request.lastBuildTimeStamp || isBuildStale(request.srcRoot, request.lastBuildTimeStamp)) { installDependencies(projectPath); return { rebuilt: true }; @@ -42,16 +42,16 @@ const runPackageManager = (cwd: string, buildArgs: string) => { } }; -const isBuildStale = (resourceDir: string, lastBuildTimestamp: Date) => { +const isBuildStale = (resourceDir: string, lastBuildTimeStamp: Date) => { const dirTime = new Date(fs.statSync(resourceDir).mtime); - if (dirTime > lastBuildTimestamp) { + if (dirTime > lastBuildTimeStamp) { return true; } const fileUpdatedAfterLastBuild = glob .sync(`${resourceDir}/*/!(build | dist)/**`) - .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimestamp); + .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimeStamp); return !!fileUpdatedAfterLastBuild; }; diff --git a/packages/amplify-java-function-runtime-provider/src/utils/invoke.ts b/packages/amplify-java-function-runtime-provider/src/utils/invoke.ts index 682cbebcc23..7ea642bc059 100644 --- a/packages/amplify-java-function-runtime-provider/src/utils/invoke.ts +++ b/packages/amplify-java-function-runtime-provider/src/utils/invoke.ts @@ -2,17 +2,9 @@ import execa from 'execa'; import path from 'path'; import { InvocationRequest } from 'amplify-function-plugin-interface'; import { packageName, relativeShimJarPath } from './constants'; -import { buildResource } from './build'; import { pathManager } from 'amplify-cli-core'; export const invokeResource = async (request: InvocationRequest, context: any) => { - await buildResource({ - env: request.env, - runtime: request.runtime, - srcRoot: request.srcRoot, - lastBuildTimestamp: request.lastBuildTimestamp, - }); - const [handlerClassName, handlerMethodName] = request.handler.split('::'); const childProcess = execa( diff --git a/packages/amplify-java-function-runtime-provider/src/utils/package.ts b/packages/amplify-java-function-runtime-provider/src/utils/package.ts index dfe89759132..2165212608f 100644 --- a/packages/amplify-java-function-runtime-provider/src/utils/package.ts +++ b/packages/amplify-java-function-runtime-provider/src/utils/package.ts @@ -3,7 +3,7 @@ import path from 'path'; import { PackageRequest, PackageResult } from 'amplify-function-plugin-interface/src'; export async function packageResource(request: PackageRequest, context: any): Promise { - if (!request.lastPackageTimestamp || request.lastBuildTimestamp > request.lastPackageTimestamp) { + if (!request.lastPackageTimeStamp || request.lastBuildTimeStamp > request.lastPackageTimeStamp) { const packageHash = (await context.amplify.hashDir(path.join(request.srcRoot, 'src'), ['build'])) as string; const output = fs.createWriteStream(request.dstFilename); diff --git a/packages/amplify-nodejs-function-runtime-provider/src/__tests__/utils/legacyBuild.test.ts b/packages/amplify-nodejs-function-runtime-provider/src/__tests__/utils/legacyBuild.test.ts index 24e348ae39c..e3d81beab57 100644 --- a/packages/amplify-nodejs-function-runtime-provider/src/__tests__/utils/legacyBuild.test.ts +++ b/packages/amplify-nodejs-function-runtime-provider/src/__tests__/utils/legacyBuild.test.ts @@ -25,7 +25,7 @@ describe('legacy build resource', () => { fs_mock.statSync.mockImplementation(file => ({ mtime: new Date(stubFileTimestamps.get(file.toString())!) } as any)); const result = await buildResource({ - lastBuildTimestamp: new Date(timestamp), + lastBuildTimeStamp: new Date(timestamp), srcRoot: 'resourceDir', env: 'something', runtime: 'other', diff --git a/packages/amplify-nodejs-function-runtime-provider/src/index.ts b/packages/amplify-nodejs-function-runtime-provider/src/index.ts index 4418ea75b32..529e0522038 100644 --- a/packages/amplify-nodejs-function-runtime-provider/src/index.ts +++ b/packages/amplify-nodejs-function-runtime-provider/src/index.ts @@ -6,12 +6,12 @@ import path from 'path'; export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactory = context => { return { - contribute: request => { + contribute: async request => { const selection = request.selection; if (selection !== 'nodejs') { - return Promise.reject(new Error(`Unknown selection ${selection}`)); + throw new Error(`Unknown selection ${selection}`); } - return Promise.resolve({ + return { runtime: { name: 'NodeJS', value: 'nodejs', @@ -37,19 +37,17 @@ export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactor }, ], }, - }); + }; }, - checkDependencies: () => Promise.resolve({ hasRequiredDependencies: true }), + checkDependencies: async () => ({ hasRequiredDependencies: true }), package: params => packageResource(params, context), - build: params => buildResource(params), - invoke: async params => { - await buildResource(params); - return invoke({ + build: buildResource, + invoke: async params => + invoke({ packageFolder: path.join(params.srcRoot, 'src'), handler: params.handler, event: params.event, environment: params.envVars, - }); - }, + }), }; }; diff --git a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts index 017e6746d84..2bd0fc04bf9 100644 --- a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts +++ b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyBuild.ts @@ -9,7 +9,7 @@ import path from 'path'; export async function buildResource(request: BuildRequest): Promise { const resourceDir = path.join(request.srcRoot, 'src'); - if (!request.lastBuildTimestamp || isBuildStale(request.srcRoot, request.lastBuildTimestamp)) { + if (!request.lastBuildTimeStamp || isBuildStale(request.srcRoot, request.lastBuildTimeStamp)) { installDependencies(resourceDir); if (request.legacyBuildHookParams) { runBuildScriptHook(request.legacyBuildHookParams.resourceName, request.legacyBuildHookParams.projectRoot); @@ -65,15 +65,15 @@ function toPackageManagerArgs(useYarn: boolean, scriptName?: string) { return useYarn ? [] : ['install']; } -function isBuildStale(resourceDir: string, lastBuildTimestamp: Date) { +function isBuildStale(resourceDir: string, lastBuildTimeStamp: Date) { const dirTime = new Date(fs.statSync(resourceDir).mtime); - if (dirTime > lastBuildTimestamp) { + if (dirTime > lastBuildTimeStamp) { return true; } const fileUpdatedAfterLastBuild = glob .sync(`${resourceDir}/**`) .filter(p => !p.includes('dist')) .filter(p => !p.includes('node_modules')) - .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimestamp); + .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimeStamp); return !!fileUpdatedAfterLastBuild; } diff --git a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyPackage.ts b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyPackage.ts index 96be46e8650..5baed223bf3 100644 --- a/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyPackage.ts +++ b/packages/amplify-nodejs-function-runtime-provider/src/utils/legacyPackage.ts @@ -4,7 +4,7 @@ import path from 'path'; import { PackageRequest, PackageResult } from 'amplify-function-plugin-interface/src'; export async function packageResource(request: PackageRequest, context: any): Promise { - if (!request.lastPackageTimestamp || request.lastBuildTimestamp > request.lastPackageTimestamp!) { + if (!request.lastPackageTimeStamp || request.lastBuildTimeStamp > request.lastPackageTimeStamp!) { const packageHash = !request.skipHashing ? ((await context.amplify.hashDir(path.join(request.srcRoot, 'src'), ['node_modules'])) as string) : undefined; diff --git a/packages/amplify-provider-awscloudformation/src/build-resources.js b/packages/amplify-provider-awscloudformation/src/build-resources.js deleted file mode 100644 index 46d238f2678..00000000000 --- a/packages/amplify-provider-awscloudformation/src/build-resources.js +++ /dev/null @@ -1,87 +0,0 @@ -import path from 'path'; -import fs from 'fs-extra'; -import { ServiceName } from 'amplify-category-function'; - -export async function run(context, category, resourceName) { - const { allResources } = await context.amplify.getResourceStatus(category, resourceName); - - const resources = allResources.filter(resource => resource.service === ServiceName.LambdaFunction).filter(resource => resource.build); - - const buildPromises = []; - for (let i = 0; i < resources.length; i += 1) { - buildPromises.push(buildResource(context, resources[i])); - } - return Promise.all(buildPromises); -} - -// This function is a translation layer around the previous buildResource -// For legacy purposes, the method builds and packages the resource -export async function buildResource(context, resource) { - const resourcePath = path.join(context.amplify.pathManager.getBackendDirPath(), resource.category, resource.resourceName); - let breadcrumbs = context.amplify.readBreadcrumbs(context, resource.category, resource.resourceName); - - let zipFilename = resource.distZipFilename; - - const runtimePlugin = await context.amplify.loadRuntimePlugin(context, breadcrumbs.pluginId); - - const depCheck = await runtimePlugin.checkDependencies(); - 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}`); - } - - // build the function - let rebuilt = false; - if (breadcrumbs.scripts && breadcrumbs.scripts.build) { - // TODO - throw new Error('Executing custom build scripts is not yet implemented'); - } else { - const buildRequest = { - env: context.amplify.getEnvInfo().envName, - srcRoot: resourcePath, - runtime: breadcrumbs.functionRuntime, - legacyBuildHookParams: { - projectRoot: context.amplify.pathManager.searchProjectRootPath(), - resourceName: resource.resourceName, - }, - lastBuildTimestamp: resource.lastBuildTimeStamp ? new Date(resource.lastBuildTimeStamp) : undefined, - }; - rebuilt = (await runtimePlugin.build(buildRequest)).rebuilt; - } - context.amplify.updateamplifyMetaAfterBuild(resource); - - // package the function - const distDir = path.join(resourcePath, 'dist'); - if (!fs.existsSync(distDir)) { - fs.mkdirSync(distDir); - } - const destination = path.join(distDir, 'latest-build.zip'); - let packagePromise; - if (breadcrumbs.scripts && breadcrumbs.scripts.package) { - // TODO - throw new Error('Executing custom package scripts is not yet implemented'); - } else { - const packageRequest = { - env: context.amplify.getEnvInfo().envName, - srcRoot: resourcePath, - dstFilename: destination, - runtime: breadcrumbs.functionRuntime, - lastPackageTimestamp: resource.lastPackageTimestamp ? new Date(resource.lastPackageTimestamp) : undefined, - lastBuildTimestamp: rebuilt ? new Date() : new Date(resource.lastBuildTimeStamp), - skipHashing: resource.skipHashing, - }; - packagePromise = runtimePlugin.package(packageRequest); - } - return new Promise((resolve, reject) => { - packagePromise - .then(result => { - const packageHash = result.packageHash; - zipFilename = packageHash - ? `${resource.resourceName}-${packageHash}-build.zip` - : zipFilename ?? `${resource.category}-${resource.resourceName}-build.zip`; - context.amplify.updateAmplifyMetaAfterPackage(resource, zipFilename); - resolve({ zipFilename, zipFilePath: destination }); - }) - .catch(err => reject(new Error(`Package command failed with error [${err}]`))); - }); -} diff --git a/packages/amplify-provider-awscloudformation/src/index.ts b/packages/amplify-provider-awscloudformation/src/index.ts index f7711058508..12c92d503be 100644 --- a/packages/amplify-provider-awscloudformation/src/index.ts +++ b/packages/amplify-provider-awscloudformation/src/index.ts @@ -3,7 +3,6 @@ const initializer = require('./initializer'); const initializeEnv = require('./initialize-env'); const resourcePusher = require('./push-resources'); const envRemover = require('./delete-env'); -const resourceBuilder = require('./build-resources'); const providerUtils = require('./utility-functions'); const constants = require('./constants'); const configManager = require('./configuration-manager'); @@ -59,10 +58,6 @@ function configure(context) { return configManager.configure(context); } -function buildResources(context, category, resourceName) { - return resourceBuilder.run(context, category, resourceName); -} - async function getConfiguredAWSClient(context, category, action) { await aws.configureWithCreds(context); category = category || 'missing'; @@ -112,7 +107,6 @@ module.exports = { constants, pushResources, storeCurrentCloudBackend, - buildResources, providerUtils, setupNewUser, getConfiguredAWSClient, diff --git a/packages/amplify-provider-awscloudformation/src/push-resources.ts b/packages/amplify-provider-awscloudformation/src/push-resources.ts index bb0c5b65895..70825cd1a9b 100644 --- a/packages/amplify-provider-awscloudformation/src/push-resources.ts +++ b/packages/amplify-provider-awscloudformation/src/push-resources.ts @@ -22,7 +22,6 @@ import { S3 } from './aws-utils/aws-s3'; import Cloudformation from './aws-utils/aws-cfn'; import { formUserAgentParam } from './aws-utils/user-agent'; import constants, { ProviderName as providerName } from './constants'; -import { buildResource } from './build-resources'; import { uploadAppSyncFiles } from './upload-appsync-files'; import { prePushGraphQLCodegen, postPushGraphQLCodegen } from './graphql-codegen'; import { prePushAuthTransform } from './auth-transform'; @@ -76,13 +75,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { parameters: { options }, } = context; - let resources: $TSObject[]; - - if (context.exeInfo && context.exeInfo.forcePush) { - resources = allResources; - } else { - resources = resourcesToBeCreated.concat(resourcesToBeUpdated); - } + const resources = !!context?.exeInfo?.forcePush ? allResources : resourcesToBeCreated.concat(resourcesToBeUpdated); await createEnvLevelConstructs(context); @@ -110,7 +103,7 @@ export async function run(context: $TSContext, resourceDefinition: $TSObject) { } } - await packageResources(context, resources); + await prepareBuildableResources(context, resources); await transformGraphQLSchema(context, { handleMigration: opts => updateStackForAPIMigration(context, 'api', undefined, opts), @@ -349,8 +342,6 @@ export async function updateStackForAPIMigration(context: $TSContext, category: resources = allResources.filter(resource => resource.service === 'AppSync'); - await packageResources(context, resources); - await uploadAppSyncFiles(context, resources, allResources, { useDeprecatedParameters: isReverting, defaultParams: { @@ -474,22 +465,26 @@ function validateCfnTemplates(context: $TSContext, resourcesToBeUpdated: $TSAny[ } } -async function packageResources(context: $TSContext, resources: $TSAny[]) { +async function prepareBuildableResources(context: $TSContext, resources: $TSAny[]) { // Only build and package resources which are required - return Promise.all(resources.filter(resource => resource.build).map(resource => packageResource(context, resource))); + return Promise.all(resources.filter(resource => resource.build).map(resource => prepareResource(context, resource))); } -async function packageResource(context: $TSContext, resource: $TSAny) { - let result: $TSAny; - let log: $TSAny = null; - const { envName }: { envName: string } = context.amplify.getEnvInfo(); - - if (resource.service === FunctionServiceNameLambdaLayer) { - result = await context.amplify.invokePluginMethod(context, 'function', undefined, 'packageLayer', [context, resource]); - } else { - result = await buildResource(context, resource); - } +async function prepareResource(context: $TSContext, resource: $TSAny) { + // currently the only category that has a "buildable" resource is the functions category, hence the hardcoding + resource.lastBuildTimeStamp = await context.amplify.invokePluginMethod(context, 'function', undefined, 'buildResource', [ + context, + resource, + ]); + const result: { zipFilename: string; zipFilePath: string } = await context.amplify.invokePluginMethod( + context, + 'function', + undefined, + 'packageResource', + [context, resource], + ); + const { envName }: { envName: string } = context.amplify.getEnvInfo(); // Upload zip file to S3 const s3Key = `amplify-builds/${result.zipFilename}`; @@ -499,7 +494,7 @@ async function packageResource(context: $TSContext, resource: $TSAny) { Body: fs.createReadStream(result.zipFilePath), Key: s3Key, }; - log = logger('packageResources.s3.uploadFile', [{ Key: s3Key }]); + const log = logger('packageResources.s3.uploadFile', [{ Key: s3Key }]); log(); let s3Bucket: string; try { diff --git a/packages/amplify-python-function-runtime-provider/src/index.ts b/packages/amplify-python-function-runtime-provider/src/index.ts index 821e792da9c..e3ee58f1e93 100644 --- a/packages/amplify-python-function-runtime-provider/src/index.ts +++ b/packages/amplify-python-function-runtime-provider/src/index.ts @@ -9,12 +9,12 @@ import { GetPackageAssetPaths } from 'amplify-cli-core'; export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactory = context => { return { - contribute: request => { + contribute: async request => { const selection = request.selection; if (selection !== 'python') { - return Promise.reject(new Error(`Unknown selection ${selection}`)); + throw new Error(`Unknown selection ${selection}`); } - return Promise.resolve({ + return { runtime: { name: 'Python', value: 'python', @@ -22,12 +22,12 @@ export const functionRuntimeContributorFactory: FunctionRuntimeContributorFactor defaultHandler: 'index.handler', layerExecutablePath: path.join('python', 'lib', 'python3.8', 'site-packages'), }, - }); + }; }, checkDependencies: checkDeps, package: request => pythonPackage(context, request), build: pythonBuild, - invoke: request => pythonBuild(request).then(() => pythonInvoke(context, request)), + invoke: request => pythonInvoke(context, request), }; }; diff --git a/packages/amplify-python-function-runtime-provider/src/util/buildUtils.ts b/packages/amplify-python-function-runtime-provider/src/util/buildUtils.ts index 59d449e1d93..102f0343a59 100644 --- a/packages/amplify-python-function-runtime-provider/src/util/buildUtils.ts +++ b/packages/amplify-python-function-runtime-provider/src/util/buildUtils.ts @@ -4,21 +4,21 @@ import { execAsStringPromise } from './pyUtils'; import glob from 'glob'; export async function pythonBuild(params: BuildRequest): Promise { - if (!params.lastBuildTimestamp || isBuildStale(params.srcRoot, params.lastBuildTimestamp)) { + if (!params.lastBuildTimeStamp || isBuildStale(params.srcRoot, params.lastBuildTimeStamp)) { const pipenvLogs = await execAsStringPromise('pipenv install', { cwd: params.srcRoot }); console.log(pipenvLogs); - return Promise.resolve({ rebuilt: true }); + return { rebuilt: true }; } - return Promise.resolve({ rebuilt: false }); + return { rebuilt: false }; } -function isBuildStale(resourceDir: string, lastBuildTimestamp: Date) { +function isBuildStale(resourceDir: string, lastBuildTimeStamp: Date) { const dirTime = new Date(fs.statSync(resourceDir).mtime); - if (dirTime > lastBuildTimestamp) { + if (dirTime > lastBuildTimeStamp) { return true; } const fileUpdatedAfterLastBuild = glob .sync(`${resourceDir}/**`, { ignore: ['**/dist/**', '**/__pycache__/**'] }) - .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimestamp); + .find(file => new Date(fs.statSync(file).mtime) > lastBuildTimeStamp); return !!fileUpdatedAfterLastBuild; } diff --git a/packages/amplify-python-function-runtime-provider/src/util/packageUtils.ts b/packages/amplify-python-function-runtime-provider/src/util/packageUtils.ts index aff382f88be..2395a29ff5d 100644 --- a/packages/amplify-python-function-runtime-provider/src/util/packageUtils.ts +++ b/packages/amplify-python-function-runtime-provider/src/util/packageUtils.ts @@ -6,7 +6,7 @@ import fs from 'fs-extra'; // packages python lambda functions and writes the archive to the specified file export async function pythonPackage(context: any, params: PackageRequest): Promise { - if (!params.lastPackageTimestamp || params.lastBuildTimestamp > params.lastPackageTimestamp) { + if (!params.lastPackageTimeStamp || params.lastBuildTimeStamp > params.lastPackageTimeStamp) { // zip source and dependencies and write to specified file const file = fs.createWriteStream(params.dstFilename); const packageHash = await context.amplify.hashDir(params.srcRoot, ['dist']);