diff --git a/README.md b/README.md index 123bef93..f97ec138 100644 --- a/README.md +++ b/README.md @@ -126,9 +126,9 @@ The following `esbuild` options are automatically set. ## Supported Runtimes -This plugin will automatically set the esbuild `target` for the following supported Serverless runtimes +This plugin will automatically set the esbuild `target` for the following supported Serverless runtimes: -AWS: +###AWS | Runtime | Target | | ------------ | -------- | @@ -137,6 +137,26 @@ AWS: | `nodejs14.x` | `node14` | | `nodejs12.x` | `node12` | +### Azure + +This plugin is compatible with the [serverless-azure-functions](https://github.com/serverless/serverless-azure-functions) plugin, and will set the runtimes accordingly. + +| Runtime | Target | +| ------------ | -------- | +| `nodejs18` | `node18` | +| `nodejs16` | `node16` | +| `nodejs14` | `node14` | +| `nodejs12` | `node12` | + +**Please Note** When using this package in conjunction with the `serverless-azure-functions` plugin, the following additional configuration is required to ensure function apps are built correctly: + +```yml +package: + patterns: ["host.json", "**/function.json"], +``` + +### Non-Node functions + If you wish to use this plugin alongside non Node functions like Python or functions with images, this plugin will automatically ignore any function which does not contain a handler or use a supported Node.js runtime. _Note:_ If you are using Python functions with Serverless Offline you will need to change the `outputWorkFolder` and `outputBuildFolder` to folder names without fullstops. diff --git a/e2e/__snapshots__/complete.test.ts.snap b/e2e/__snapshots__/complete.test.ts.snap index 9e56ab2f..13f78749 100644 --- a/e2e/__snapshots__/complete.test.ts.snap +++ b/e2e/__snapshots__/complete.test.ts.snap @@ -171,6 +171,7 @@ exports[`complete 5`] = ` "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", + "logs:TagResource", ], "Effect": "Allow", "Resource": [ diff --git a/e2e/__snapshots__/individually.test.ts.snap b/e2e/__snapshots__/individually.test.ts.snap index 785b7650..9681e60c 100644 --- a/e2e/__snapshots__/individually.test.ts.snap +++ b/e2e/__snapshots__/individually.test.ts.snap @@ -390,6 +390,7 @@ exports[`individually 6`] = ` "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", + "logs:TagResource", ], "Effect": "Allow", "Resource": [ diff --git a/e2e/__snapshots__/minimal.test.ts.snap b/e2e/__snapshots__/minimal.test.ts.snap index 35f9c52a..cb814a22 100644 --- a/e2e/__snapshots__/minimal.test.ts.snap +++ b/e2e/__snapshots__/minimal.test.ts.snap @@ -14087,6 +14087,7 @@ exports[`minimal 5`] = ` "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", + "logs:TagResource", ], "Effect": "Allow", "Resource": [ diff --git a/src/helper.ts b/src/helper.ts index bc7b79a7..4c7dfd0a 100644 --- a/src/helper.ts +++ b/src/helper.ts @@ -217,21 +217,29 @@ export type AwsNodeProviderRuntimeMatcher = { [Version in Versions as `nodejs${Version}.x`]: `node${Version}`; }; +export type AzureNodeProviderRuntimeMatcher = { + [Version in Versions as `nodejs${Version}`]: `node${Version}`; +}; + export type GoogleNodeProviderRuntimeMatcher = { [Version in Versions as `nodejs${Version}`]: `node${Version}`; }; export type AwsNodeMatcher = AwsNodeProviderRuntimeMatcher<12 | 14 | 16 | 18>; +export type AzureNodeMatcher = AzureNodeProviderRuntimeMatcher<12 | 14 | 16 | 18>; + export type GoogleNodeMatcher = GoogleNodeProviderRuntimeMatcher<12 | 14 | 16 | 18>; -export type NodeMatcher = AwsNodeMatcher & GoogleNodeMatcher; +export type NodeMatcher = AwsNodeMatcher & AzureNodeMatcher & GoogleNodeMatcher; export type AwsNodeMatcherKey = keyof AwsNodeMatcher; +export type AzureNodeMatcherKey = keyof AzureNodeMatcher; + export type GoogleNodeMatcherKey = keyof GoogleNodeMatcher; -export type NodeMatcherKey = AwsNodeMatcherKey | GoogleNodeMatcherKey; +export type NodeMatcherKey = AwsNodeMatcherKey | AzureNodeMatcherKey | GoogleNodeMatcherKey; const awsNodeMatcher: AwsNodeMatcher = { 'nodejs18.x': 'node18', @@ -240,6 +248,13 @@ const awsNodeMatcher: AwsNodeMatcher = { 'nodejs12.x': 'node12', }; +const azureNodeMatcher: AzureNodeMatcher = { + nodejs18: 'node18', + nodejs16: 'node16', + nodejs14: 'node14', + nodejs12: 'node12', +}; + const googleNodeMatcher: GoogleNodeMatcher = { nodejs18: 'node18', nodejs16: 'node16', @@ -247,10 +262,11 @@ const googleNodeMatcher: GoogleNodeMatcher = { nodejs12: 'node12', }; -const nodeMatcher: NodeMatcher = { ...googleNodeMatcher, ...awsNodeMatcher }; +const nodeMatcher: NodeMatcher = { ...googleNodeMatcher, ...awsNodeMatcher, ...azureNodeMatcher }; export const providerRuntimeMatcher = Object.freeze>({ aws: awsNodeMatcher as NodeMatcher, + azure: azureNodeMatcher as NodeMatcher, google: googleNodeMatcher as NodeMatcher, }); diff --git a/src/tests/helper.test.ts b/src/tests/helper.test.ts index 3f3f8850..ae30d48f 100644 --- a/src/tests/helper.test.ts +++ b/src/tests/helper.test.ts @@ -10,7 +10,7 @@ jest.mock('fs-extra'); const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); -afterEach(() => { +afterAll(() => { jest.resetAllMocks(); }); @@ -176,6 +176,166 @@ describe('extractFunctionEntries', () => { expect(consoleSpy).toBeCalled(); }); }); + + describe('azure', () => { + it('should return entries for handlers which reference files in the working directory', () => { + jest.mocked(fs.existsSync).mockReturnValue(true); + const functionDefinitions = { + function1: { + events: [], + handler: 'file1.handler', + }, + function2: { + events: [], + handler: 'file2.handler', + }, + }; + + const fileNames = extractFunctionEntries(cwd, 'azure', functionDefinitions); + + expect(fileNames).toStrictEqual([ + { + entry: 'file1.ts', + func: functionDefinitions.function1, + functionAlias: 'function1', + }, + { + entry: 'file2.ts', + func: functionDefinitions.function2, + functionAlias: 'function2', + }, + ]); + }); + + it('should return entries for handlers which reference directories that contain index files', () => { + jest.mocked(fs.existsSync).mockImplementation((fPath) => { + return typeof fPath !== 'string' || fPath.endsWith('/index.ts'); + }); + + const functionDefinitions = { + function1: { + events: [], + handler: 'dir1.handler', + }, + function2: { + events: [], + handler: 'dir2.handler', + }, + }; + + const fileNames = extractFunctionEntries(cwd, 'azure', functionDefinitions); + + expect(fileNames).toStrictEqual([ + { + entry: 'dir1/index.ts', + func: functionDefinitions.function1, + functionAlias: 'function1', + }, + { + entry: 'dir2/index.ts', + func: functionDefinitions.function2, + functionAlias: 'function2', + }, + ]); + }); + + it('should return entries for handlers which reference files in folders in the working directory', () => { + jest.mocked(fs.existsSync).mockReturnValue(true); + const functionDefinitions = { + function1: { + events: [], + handler: 'folder/file1.handler', + }, + function2: { + events: [], + handler: 'folder/file2.handler', + }, + }; + + const fileNames = extractFunctionEntries(cwd, 'azure', functionDefinitions); + + expect(fileNames).toStrictEqual([ + { + entry: 'folder/file1.ts', + func: functionDefinitions.function1, + functionAlias: 'function1', + }, + { + entry: 'folder/file2.ts', + func: functionDefinitions.function2, + functionAlias: 'function2', + }, + ]); + }); + + it('should return entries for handlers which reference files using a relative path in the working directory', () => { + jest.mocked(fs.existsSync).mockReturnValue(true); + const functionDefinitions = { + function1: { + events: [], + handler: './file1.handler', + }, + function2: { + events: [], + handler: './file2.handler', + }, + }; + + const fileNames = extractFunctionEntries(cwd, 'azure', functionDefinitions); + + expect(fileNames).toStrictEqual([ + { + entry: 'file1.ts', + func: functionDefinitions.function1, + functionAlias: 'function1', + }, + { + entry: 'file2.ts', + func: functionDefinitions.function2, + functionAlias: 'function2', + }, + ]); + }); + + it('should return entries for handlers on a Windows platform', () => { + jest.mocked(fs.existsSync).mockReturnValue(true); + jest.spyOn(path, 'relative').mockReturnValueOnce('src\\file1.ts'); + jest.spyOn(os, 'platform').mockReturnValueOnce('win32'); + const functionDefinitions = { + function1: { + events: [], + handler: 'file1.handler', + }, + }; + + const fileNames = extractFunctionEntries(cwd, 'azure', functionDefinitions); + + expect(fileNames).toStrictEqual([ + { + entry: 'src/file1.ts', + func: functionDefinitions.function1, + functionAlias: 'function1', + }, + ]); + }); + + it('should throw an error if the handlers reference a file which does not exist', () => { + jest.mocked(fs.existsSync).mockReturnValue(false); + const functionDefinitions = { + function1: { + events: [], + handler: 'file1.handler', + }, + function2: { + events: [], + handler: 'file2.handler', + }, + }; + + expect(() => extractFunctionEntries(cwd, 'azure', functionDefinitions)).toThrowError(); + expect(consoleSpy).toBeCalled(); + }); + }); }); describe('getDepsFromBundle', () => {