Skip to content

Commit

Permalink
defineFunction initial implementation (#742)
Browse files Browse the repository at this point in the history
* build working

* update package lock

* updating test cases

* update snapshots

* update api extract

* add changeset

* fix api

* fix auto-rename mishap

* update e2e test case

* add additional check to e2e suite

* lint fixes

* update snapshots

* trying this

* updating assertion

* update snapshots

* addressing PR comments

* update snapshots

* update api extract

* update changeset

* fix lint

* Im dumb

* still dumb

* remove unused code

* hopefully last fix

* update snapshots

* json parse lambda output
  • Loading branch information
edwardfoyle authored Nov 30, 2023
1 parent db775ad commit c6c39d0
Show file tree
Hide file tree
Showing 85 changed files with 1,112 additions and 671 deletions.
5 changes: 5 additions & 0 deletions .changeset/few-carpets-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-function': minor
---

Initial implementation of new 'defineFunction' entry point
7 changes: 7 additions & 0 deletions .changeset/mighty-rockets-shake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'create-amplify': patch
'@aws-amplify/plugin-types': minor
'@aws-amplify/backend': minor
---

Expose new `defineFunction` interface
506 changes: 497 additions & 9 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 7 additions & 24 deletions packages/backend-function/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,20 @@
```ts

import { AmplifyFunctionProps } from '@aws-amplify/function-construct-alpha';
import { AmplifyLambdaFunction } from '@aws-amplify/function-construct-alpha';
import { Construct } from 'constructs';
import { ConstructFactory } from '@aws-amplify/plugin-types';
import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types';
import { FunctionResources } from '@aws-amplify/plugin-types';
import { ResourceProvider } from '@aws-amplify/plugin-types';

// @public
export class AmplifyFunctionFactory implements ConstructFactory<AmplifyLambdaFunction> {
static build: (props: AmplifyFunctionFactoryBuildProps) => Promise<AmplifyFunctionFactory>;
static fromDir: (props: AmplifyFunctionFactoryFromDirProps) => AmplifyFunctionFactory;
getInstance: ({ constructContainer, }: ConstructFactoryGetInstanceProps) => AmplifyLambdaFunction;
}
export const defineFunction: (props?: FunctionProps) => ConstructFactory<Construct & ResourceProvider<FunctionResources>>;

// @public (undocumented)
export type AmplifyFunctionFactoryBaseProps = {
name: string;
export type FunctionProps = {
name?: string;
entry?: string;
};

// @public (undocumented)
export type AmplifyFunctionFactoryBuildProps = AmplifyFunctionFactoryBaseProps & Omit<AmplifyFunctionProps, 'absoluteCodePath'> & {
buildCommand: string;
outDir: string;
};

// @public (undocumented)
export type AmplifyFunctionFactoryFromDirProps = AmplifyFunctionFactoryBaseProps & Omit<AmplifyFunctionProps, 'absoluteCodePath'> & {
codePath: string;
};

// @public
export const Func: typeof AmplifyFunctionFactory;

// (No @packageDocumentation comment for this package)

```
4 changes: 2 additions & 2 deletions packages/backend-function/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
"license": "Apache-2.0",
"dependencies": {
"@aws-amplify/backend-output-storage": "0.2.6",
"@aws-amplify/function-construct-alpha": "^0.2.1",
"@aws-amplify/plugin-types": "^0.4.2",
"execa": "^7.1.1"
},
"devDependencies": {
"@aws-amplify/backend-platform-test-stubs": "^0.3.1",
"@aws-amplify/platform-core": "^0.3.1"
"@aws-amplify/platform-core": "^0.3.1",
"uuid": "^9.0.1"
},
"peerDependencies": {
"aws-cdk-lib": "^2.110.1",
Expand Down
95 changes: 66 additions & 29 deletions packages/backend-function/src/factory.test.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { beforeEach, describe, it, mock } from 'node:test';
import { Func } from './factory.js';
import { beforeEach, describe, it } from 'node:test';
import { App, Stack } from 'aws-cdk-lib';
import { ConstructFactoryGetInstanceProps } from '@aws-amplify/plugin-types';
import assert from 'node:assert';
import { fileURLToPath } from 'url';
import * as path from 'path';
import { StackMetadataBackendOutputStorageStrategy } from '@aws-amplify/backend-output-storage';
import {
ConstructContainerStub,
StackResolverStub,
} from '@aws-amplify/backend-platform-test-stubs';
import { defaultLambda } from './test-assets/default-lambda/resource.js';
import { Template } from 'aws-cdk-lib/assertions';
import { defineFunction } from './factory.js';
import { lambdaWithDependencies } from './test-assets/lambda-with-dependencies/resource.js';

const createStackAndSetContext = (): Stack => {
const app = new App();
Expand Down Expand Up @@ -41,38 +42,74 @@ void describe('AmplifyFunctionFactory', () => {
});

void it('creates singleton function instance', () => {
const functionFactory = Func.fromDir({
name: 'testFunc',
codePath: path.join('..', 'test-assets', 'test-lambda'),
});
const functionFactory = defaultLambda;
const instance1 = functionFactory.getInstance(getInstanceProps);
const instance2 = functionFactory.getInstance(getInstanceProps);
assert.strictEqual(instance1, instance2);
});

void it('executes build command from directory where constructor is used', async () => {
const commandExecutorMock = mock.fn();
void it('resolves default name and entry when no args specified', () => {
const functionFactory = defaultLambda;
const lambda = functionFactory.getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda));
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.handler',
});
const lambdaLogicalId = Object.keys(
template.findResources('AWS::Lambda::Function')
)[0];
// eslint-disable-next-line spellcheck/spell-checker
assert.ok(lambdaLogicalId.includes('defaultlambda'));
});

// Casting to never is necessary because commandExecutor is a private method.
// TS yells that it's not a property on Func even though it is there
mock.method(Func, 'commandExecutor' as never, commandExecutorMock);
void it('resolves default name when entry specified', () => {
const functionFactory = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
});
const lambda = functionFactory.getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda));
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.handler',
});
const lambdaLogicalId = Object.keys(
template.findResources('AWS::Lambda::Function')
)[0];
assert.ok(lambdaLogicalId.includes('handler'));
});

(
await Func.build({
name: 'testFunc',
outDir: path.join('..', 'test-assets', 'test-lambda'),
buildCommand: 'test command',
})
).getInstance(getInstanceProps);
void it('uses name and entry that is explicitly specified', () => {
const functionFactory = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
name: 'myCoolLambda',
});
const lambda = functionFactory.getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda));
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.handler',
});
const lambdaLogicalId = Object.keys(
template.findResources('AWS::Lambda::Function')
)[0];
assert.ok(lambdaLogicalId.includes('myCoolLambda'));
});

assert.strictEqual(commandExecutorMock.mock.callCount(), 1);
assert.deepStrictEqual(commandExecutorMock.mock.calls[0].arguments, [
'test command',
{
cwd: fileURLToPath(new URL('../src', import.meta.url)),
stdio: 'inherit',
shell: 'bash',
},
]);
void it('builds lambda with local and 3p dependencies', () => {
const lambda = lambdaWithDependencies.getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda));
// There isn't a way to check the contents of the bundled lambda using the CDK Template utility
// So we just check that the lambda was created properly in the CFN template.
// There is an e2e test that validates proper lambda bundling
template.resourceCountIs('AWS::Lambda::Function', 1);
template.hasResourceProperties('AWS::Lambda::Function', {
Handler: 'index.handler',
});
const lambdaLogicalId = Object.keys(
template.findResources('AWS::Lambda::Function')
)[0];
// eslint-disable-next-line spellcheck/spell-checker
assert.ok(lambdaLogicalId.includes('lambdawithdependencies'));
});
});
Loading

0 comments on commit c6c39d0

Please sign in to comment.