diff --git a/layers/.eslintrc.js b/layers/.eslintrc.js index b83ce24ffc..8e1dd1513f 100644 --- a/layers/.eslintrc.js +++ b/layers/.eslintrc.js @@ -5,63 +5,67 @@ module.exports = { jest: true, node: true, }, - extends: [ 'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/recommended' ], + extends: [ + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ], parser: '@typescript-eslint/parser', - plugins: ['@typescript-eslint'], + plugins: ['@typescript-eslint', 'prettier'], settings: { 'import/resolver': { node: {}, typescript: { - project: './tsconfig.es.json', + project: './tsconfig.json', alwaysTryTypes: true, }, }, }, rules: { - '@typescript-eslint/ban-ts-ignore': ['off'], - '@typescript-eslint/camelcase': ['off'], - '@typescript-eslint/explicit-function-return-type': [ 'error', { allowExpressions: true } ], - '@typescript-eslint/explicit-member-accessibility': 'error', - '@typescript-eslint/indent': [ 'error', 2, { SwitchCase: 1 } ], - '@typescript-eslint/interface-name-prefix': ['off'], - '@typescript-eslint/member-delimiter-style': [ 'error', { multiline: { delimiter: 'none' } } ], + '@typescript-eslint/explicit-function-return-type': [ + 'error', + { allowExpressions: true }, + ], // Enforce return type definitions for functions + '@typescript-eslint/explicit-member-accessibility': 'error', // Enforce explicit accessibility modifiers on class properties and methods (public, private, protected) '@typescript-eslint/member-ordering': [ + // Standardize the order of class members 'error', { default: { memberTypes: [ 'signature', - 'public-field', // = ["public-static-field", "public-instance-field"] - 'protected-field', // = ["protected-static-field", "protected-instance-field"] - 'private-field', // = ["private-static-field", "private-instance-field"] + 'public-field', + 'protected-field', + 'private-field', 'constructor', - 'public-method', // = ["public-static-method", "public-instance-method"] - 'protected-method', // = ["protected-static-method", "protected-instance-method"] - 'private-method', // = ["private-static-method", "private-instance-method"] + 'public-method', + 'protected-method', + 'private-method', ], order: 'alphabetically', }, }, ], - '@typescript-eslint/no-explicit-any': 'error', - '@typescript-eslint/no-inferrable-types': ['off'], - '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_' } ], - '@typescript-eslint/no-use-before-define': ['off'], - '@typescript-eslint/semi': [ 'error', 'always' ], - 'array-bracket-spacing': [ 'error', 'always', { singleValue: false } ], - 'arrow-body-style': [ 'error', 'as-needed' ], - 'computed-property-spacing': [ 'error', 'never' ], - 'func-style': [ 'warn', 'expression' ], - indent: [ 'error', 2, { SwitchCase: 1 } ], - 'keyword-spacing': 'error', - 'newline-before-return': 2, - 'no-console': 0, - 'no-multi-spaces': [ 'error', { ignoreEOLComments: false } ], - 'no-multiple-empty-lines': [ 'error', { max: 1, maxBOF: 0 } ], - 'no-throw-literal': 'error', - 'object-curly-spacing': [ 'error', 'always' ], - 'prefer-arrow-callback': 'error', - quotes: [ 'error', 'single', { allowTemplateLiterals: true } ], - semi: [ 'error', 'always' ] + '@typescript-eslint/no-explicit-any': 'error', // Disallow usage of the any type + '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }], // Disallow unused variables, except for variables starting with an underscore + '@typescript-eslint/no-use-before-define': ['off'], // Check if this rule is needed + 'no-unused-vars': 'off', // Disable eslint core rule, since it's replaced by @typescript-eslint/no-unused-vars + // Rules from eslint core https://eslint.org/docs/latest/rules/ + 'array-bracket-spacing': ['error', 'never'], // Disallow spaces inside of array brackets + 'computed-property-spacing': ['error', 'never'], // Disallow spaces inside of computed properties + 'func-style': ['warn', 'expression'], // Enforce function expressions instead of function declarations + 'keyword-spacing': 'error', // Enforce spaces after keywords and before parenthesis, e.g. if (condition) instead of if(condition) + 'padding-line-between-statements': [ + // Require an empty line before return statements + 'error', + { blankLine: 'always', prev: '*', next: 'return' }, + ], + 'no-console': 0, // Allow console.log statements + 'no-multi-spaces': ['error', { ignoreEOLComments: false }], // Disallow multiple spaces except for comments + 'no-multiple-empty-lines': ['error', { max: 1, maxBOF: 0, maxEOF: 0 }], // Enforce no empty line at the beginning & end of files and max 1 empty line between consecutive statements + 'no-throw-literal': 'error', // Disallow throwing literals as exceptions, e.g. throw 'error' instead of throw new Error('error') + 'object-curly-spacing': ['error', 'always'], // Enforce spaces inside of curly braces in objects + 'prefer-arrow-callback': 'error', // Enforce arrow functions instead of anonymous functions for callbacks + quotes: ['error', 'single', { allowTemplateLiterals: true }], // Enforce single quotes except for template strings + semi: ['error', 'always'], // Require semicolons instead of ASI (automatic semicolon insertion) at the end of statements }, }; diff --git a/layers/bin/layers.ts b/layers/bin/layers.ts index e5eea801e4..902bd6de5f 100644 --- a/layers/bin/layers.ts +++ b/layers/bin/layers.ts @@ -11,4 +11,4 @@ new LayerPublisherStack(app, 'LayerPublisherStack', { powertoolsPackageVersion: app.node.tryGetContext('PowertoolsPackageVersion'), layerName: 'AWSLambdaPowertoolsTypeScript', ssmParameterLayerArn: SSM_PARAM_LAYER_ARN, -}); \ No newline at end of file +}); diff --git a/layers/jest.config.js b/layers/jest.config.js index 4c5e481d90..6476c09626 100644 --- a/layers/jest.config.js +++ b/layers/jest.config.js @@ -8,38 +8,21 @@ module.exports = { transform: { '^.+\\.ts?$': 'ts-jest', }, - moduleFileExtensions: [ 'js', 'ts' ], - 'collectCoverageFrom': [ - '**/src/**/*.ts', - '!**/node_modules/**', - ], - 'testMatch': ['**/?(*.)+(spec|test).ts'], - 'roots': [ - '/src', - '/tests', - ], - 'testPathIgnorePatterns': [ - '/node_modules/', - ], - 'testEnvironment': 'node', - 'coveragePathIgnorePatterns': [ - '/node_modules/', - '/types/', - ], - 'coverageThreshold': { - 'global': { - 'statements': 100, - 'branches': 100, - 'functions': 100, - 'lines': 100, + moduleFileExtensions: ['js', 'ts'], + collectCoverageFrom: ['**/src/**/*.ts', '!**/node_modules/**'], + testMatch: ['**/?(*.)+(spec|test).ts'], + roots: ['/src', '/tests'], + testPathIgnorePatterns: ['/node_modules/'], + testEnvironment: 'node', + coveragePathIgnorePatterns: ['/node_modules/', '/types/'], + coverageThreshold: { + global: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, }, }, - 'coverageReporters': [ - 'json-summary', - 'text', - 'lcov' - ], - 'setupFiles': [ - '/tests/helpers/populateEnvironmentVariables.ts' - ] -}; \ No newline at end of file + coverageReporters: ['json-summary', 'text', 'lcov'], + setupFiles: ['/tests/helpers/populateEnvironmentVariables.ts'], +}; diff --git a/layers/package.json b/layers/package.json index 194e3bdf3d..a7158461c4 100644 --- a/layers/package.json +++ b/layers/package.json @@ -12,13 +12,14 @@ "test": "echo 'Not applicable'", "cdk": "cdk", "package": "echo 'Not applicable'", - "lint": "eslint --ext .ts --no-error-on-unmatched-pattern src tests", - "lint-fix": "eslint --fix --ext .ts --fix --no-error-on-unmatched-pattern src tests", + "lint": "eslint --ext .ts,.js --no-error-on-unmatched-pattern .", + "lint-fix": "eslint --fix --ext .ts,.js --fix --no-error-on-unmatched-pattern .", "test:unit": "jest --group=unit", "test:e2e": "RUNTIME=nodejs14x jest --group=e2e" }, "lint-staged": { - "*.ts": "npm run lint-fix" + "*.ts": "npm run lint-fix", + "*.js": "npm run lint-fix" }, "repository": { "type": "git", @@ -36,4 +37,4 @@ "devDependencies": { "source-map-support": "^0.5.21" } -} +} \ No newline at end of file diff --git a/layers/src/layer-publisher-stack.ts b/layers/src/layer-publisher-stack.ts index 98e4f03529..e55eac4dca 100644 --- a/layers/src/layer-publisher-stack.ts +++ b/layers/src/layer-publisher-stack.ts @@ -1,9 +1,4 @@ -import { - CfnOutput, - RemovalPolicy, - Stack, - StackProps -} from 'aws-cdk-lib'; +import { CfnOutput, RemovalPolicy, Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { LayerVersion, @@ -14,19 +9,25 @@ import { import { StringParameter } from 'aws-cdk-lib/aws-ssm'; export interface LayerPublisherStackProps extends StackProps { - readonly layerName?: string - readonly powertoolsPackageVersion?: string - readonly ssmParameterLayerArn: string + readonly layerName?: string; + readonly powertoolsPackageVersion?: string; + readonly ssmParameterLayerArn: string; } export class LayerPublisherStack extends Stack { public readonly lambdaLayerVersion: LayerVersion; - public constructor(scope: Construct, id: string, props: LayerPublisherStackProps) { + public constructor( + scope: Construct, + id: string, + props: LayerPublisherStackProps + ) { super(scope, id, props); const { layerName, powertoolsPackageVersion } = props; - console.log(`publishing layer ${layerName} version : ${powertoolsPackageVersion}`); + console.log( + `publishing layer ${layerName} version : ${powertoolsPackageVersion}` + ); this.lambdaLayerVersion = new LayerVersion(this, 'LambdaPowertoolsLayer', { layerVersionName: props?.layerName, @@ -34,7 +35,7 @@ export class LayerPublisherStack extends Stack { compatibleRuntimes: [ Runtime.NODEJS_14_X, Runtime.NODEJS_16_X, - Runtime.NODEJS_18_X + Runtime.NODEJS_18_X, ], license: 'MIT-0', // This is needed because the following regions do not support the compatibleArchitectures property #1400 @@ -42,11 +43,15 @@ export class LayerPublisherStack extends Stack { code: Code.fromAsset('../tmp'), }); - const layerPermission = new CfnLayerVersionPermission(this, 'PublicLayerAccess', { - action: 'lambda:GetLayerVersion', - layerVersionArn: this.lambdaLayerVersion.layerVersionArn, - principal: '*', - }); + const layerPermission = new CfnLayerVersionPermission( + this, + 'PublicLayerAccess', + { + action: 'lambda:GetLayerVersion', + layerVersionArn: this.lambdaLayerVersion.layerVersionArn, + principal: '*', + } + ); layerPermission.applyRemovalPolicy(RemovalPolicy.RETAIN); this.lambdaLayerVersion.applyRemovalPolicy(RemovalPolicy.RETAIN); diff --git a/layers/tests/e2e/constants.ts b/layers/tests/e2e/constants.ts index 268910c49b..e881a06b0e 100644 --- a/layers/tests/e2e/constants.ts +++ b/layers/tests/e2e/constants.ts @@ -2,4 +2,4 @@ export const RESOURCE_NAME_PREFIX = 'Layers-E2E'; export const ONE_MINUTE = 60 * 1000; export const TEST_CASE_TIMEOUT = 3 * ONE_MINUTE; export const SETUP_TIMEOUT = 5 * ONE_MINUTE; -export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; \ No newline at end of file +export const TEARDOWN_TIMEOUT = 5 * ONE_MINUTE; diff --git a/layers/tests/e2e/layerPublisher.class.test.functionCode.ts b/layers/tests/e2e/layerPublisher.class.test.functionCode.ts index aaee959338..6606efffbc 100644 --- a/layers/tests/e2e/layerPublisher.class.test.functionCode.ts +++ b/layers/tests/e2e/layerPublisher.class.test.functionCode.ts @@ -4,20 +4,22 @@ import { Metrics } from '@aws-lambda-powertools/metrics'; import { Tracer } from '@aws-lambda-powertools/tracer'; const logger = new Logger({ - logLevel: 'DEBUG' + logLevel: 'DEBUG', }); const metrics = new Metrics(); const tracer = new Tracer(); export const handler = (): void => { - // Check that the packages version matches the expected one try { const packageJSON = JSON.parse( - readFileSync('/opt/nodejs/node_modules/@aws-lambda-powertools/logger/package.json', { - encoding: 'utf8', - flag: 'r', - }) + readFileSync( + '/opt/nodejs/node_modules/@aws-lambda-powertools/logger/package.json', + { + encoding: 'utf8', + flag: 'r', + } + ) ); if (packageJSON.version != process.env.POWERTOOLS_PACKAGE_VERSION) { @@ -42,5 +44,4 @@ export const handler = (): void => { tracer.annotateColdStart(); handlerSegment.close(); tracer.setSegment(segment); - -}; \ No newline at end of file +}; diff --git a/layers/tests/e2e/layerPublisher.test.ts b/layers/tests/e2e/layerPublisher.test.ts index 378bfab1a2..35efdefae7 100644 --- a/layers/tests/e2e/layerPublisher.test.ts +++ b/layers/tests/e2e/layerPublisher.test.ts @@ -8,23 +8,23 @@ import { Tracing } from 'aws-cdk-lib/aws-lambda'; import { LayerPublisherStack } from '../../src/layer-publisher-stack'; import { deployStack, - destroyStack + destroyStack, } from '../../../packages/commons/tests/utils/cdk-cli'; import { generateUniqueName, invokeFunction, isValidRuntimeKey, - createStackWithLambdaFunction + createStackWithLambdaFunction, } from '../../../packages/commons/tests/utils/e2eUtils'; import { RESOURCE_NAME_PREFIX, SETUP_TIMEOUT, TEARDOWN_TIMEOUT, - TEST_CASE_TIMEOUT + TEST_CASE_TIMEOUT, } from './constants'; import { LEVEL, - InvocationLogs + InvocationLogs, } from '../../../packages/commons/tests/utils/InvocationLogs'; import { v4 } from 'uuid'; import path from 'path'; @@ -37,13 +37,32 @@ if (!isValidRuntimeKey(runtime)) { } describe(`layers E2E tests (LayerPublisherStack) for runtime: ${runtime}`, () => { - const uuid = v4(); let invocationLogs: InvocationLogs[]; - const stackNameLayers = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'layerStack'); - const stackNameFunction = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'functionStack'); - const functionName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'function'); - const ssmParameterLayerName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'parameter'); + const stackNameLayers = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'layerStack' + ); + const stackNameFunction = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'functionStack' + ); + const functionName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'function' + ); + const ssmParameterLayerName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'parameter' + ); const lambdaFunctionCodeFile = 'layerPublisher.class.test.functionCode.ts'; const invocationCount = 1; @@ -55,19 +74,19 @@ describe(`layers E2E tests (LayerPublisherStack) for runtime: ${runtime}`, () => const powerToolsPackageVersion = packageJson.version; beforeAll(async () => { - - const layerName = generateUniqueName(RESOURCE_NAME_PREFIX, uuid, runtime, 'layer'); - - stackLayer = new LayerPublisherStack( - integTestApp, - stackNameLayers, - { - layerName: layerName, - powertoolsPackageVersion: powerToolsPackageVersion, - ssmParameterLayerArn: ssmParameterLayerName, - } + const layerName = generateUniqueName( + RESOURCE_NAME_PREFIX, + uuid, + runtime, + 'layer' ); + stackLayer = new LayerPublisherStack(integTestApp, stackNameLayers, { + layerName: layerName, + powertoolsPackageVersion: powerToolsPackageVersion, + ssmParameterLayerArn: ssmParameterLayerName, + }); + stackFunction = createStackWithLambdaFunction({ app: integTestApp, stackName: stackNameFunction, @@ -85,8 +104,8 @@ describe(`layers E2E tests (LayerPublisherStack) for runtime: ${runtime}`, () => '@aws-lambda-powertools/commons', '@aws-lambda-powertools/logger', '@aws-lambda-powertools/metrics', - '@aws-lambda-powertools/tracer' - ] + '@aws-lambda-powertools/tracer', + ], }, layers: [stackLayer.lambdaLayerVersion], }); @@ -94,47 +113,56 @@ describe(`layers E2E tests (LayerPublisherStack) for runtime: ${runtime}`, () => await deployStack(integTestApp, stackLayer); await deployStack(integTestApp, stackFunction); - invocationLogs = await invokeFunction(functionName, invocationCount, 'SEQUENTIAL'); - + invocationLogs = await invokeFunction( + functionName, + invocationCount, + 'SEQUENTIAL' + ); }, SETUP_TIMEOUT); describe('LayerPublisherStack usage', () => { + it( + 'should have no errors in the logs, which indicates the pacakges version matches the expected one', + () => { + const logs = invocationLogs[0].getFunctionLogs(LEVEL.ERROR); - it('should have no errors in the logs, which indicates the pacakges version matches the expected one', () => { - - const logs = invocationLogs[0].getFunctionLogs(LEVEL.ERROR); - - expect(logs.length).toBe(0); - - }, TEST_CASE_TIMEOUT); - - it('should have one warning related to missing Metrics namespace', () => { - - const logs = invocationLogs[0].getFunctionLogs(LEVEL.WARN); - - expect(logs.length).toBe(1); - expect(logs[0]).toContain('Namespace should be defined, default used'); - - }, TEST_CASE_TIMEOUT); - - it('should have one info log related to coldstart metric', () => { - - const logs = invocationLogs[0].getFunctionLogs(LEVEL.INFO); - - expect(logs.length).toBe(1); - expect(logs[0]).toContain('ColdStart'); + expect(logs.length).toBe(0); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); + it( + 'should have one warning related to missing Metrics namespace', + () => { + const logs = invocationLogs[0].getFunctionLogs(LEVEL.WARN); - it('should have one debug log that says Hello World!', () => { + expect(logs.length).toBe(1); + expect(logs[0]).toContain('Namespace should be defined, default used'); + }, + TEST_CASE_TIMEOUT + ); - const logs = invocationLogs[0].getFunctionLogs(LEVEL.DEBUG); + it( + 'should have one info log related to coldstart metric', + () => { + const logs = invocationLogs[0].getFunctionLogs(LEVEL.INFO); - expect(logs.length).toBe(1); - expect(logs[0]).toContain('Hello World!'); + expect(logs.length).toBe(1); + expect(logs[0]).toContain('ColdStart'); + }, + TEST_CASE_TIMEOUT + ); - }, TEST_CASE_TIMEOUT); + it( + 'should have one debug log that says Hello World!', + () => { + const logs = invocationLogs[0].getFunctionLogs(LEVEL.DEBUG); + expect(logs.length).toBe(1); + expect(logs[0]).toContain('Hello World!'); + }, + TEST_CASE_TIMEOUT + ); }); afterAll(async () => { @@ -143,5 +171,4 @@ describe(`layers E2E tests (LayerPublisherStack) for runtime: ${runtime}`, () => await destroyStack(integTestApp, stackLayer); } }, TEARDOWN_TIMEOUT); - -}); \ No newline at end of file +}); diff --git a/layers/tests/helpers/populateEnvironmentVariables.ts b/layers/tests/helpers/populateEnvironmentVariables.ts index 7ff0774273..acb0fd0595 100644 --- a/layers/tests/helpers/populateEnvironmentVariables.ts +++ b/layers/tests/helpers/populateEnvironmentVariables.ts @@ -1,7 +1,11 @@ // Reserved variables -process.env._X_AMZN_TRACE_ID = 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; +process.env._X_AMZN_TRACE_ID = + 'Root=1-5759e988-bd862e3fe1be46a994272793;Parent=557abcec3ee5a047;Sampled=1'; process.env.AWS_LAMBDA_FUNCTION_NAME = 'my-lambda-function'; process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE = '128'; -if (process.env.AWS_REGION === undefined && process.env.CDK_DEFAULT_REGION === undefined) { +if ( + process.env.AWS_REGION === undefined && + process.env.CDK_DEFAULT_REGION === undefined +) { process.env.AWS_REGION = 'eu-west-1'; } diff --git a/layers/tests/unit/layer-publisher.test.ts b/layers/tests/unit/layer-publisher.test.ts index ef1e932a12..6dba9993c6 100644 --- a/layers/tests/unit/layer-publisher.test.ts +++ b/layers/tests/unit/layer-publisher.test.ts @@ -9,9 +9,7 @@ import { Template } from 'aws-cdk-lib/assertions'; import { LayerPublisherStack } from '../../src/layer-publisher-stack'; describe('Class: LayerPublisherStack', () => { - it('creates the stack with a layer in it', () => { - // Prepare const app = new App(); const stack = new LayerPublisherStack(app, 'MyTestStack', { @@ -26,11 +24,7 @@ describe('Class: LayerPublisherStack', () => { // Assess template.resourceCountIs('AWS::Lambda::LayerVersion', 1); template.hasResourceProperties('AWS::Lambda::LayerVersion', { - CompatibleRuntimes: [ - 'nodejs14.x', - 'nodejs16.x', - 'nodejs18.x' - ], + CompatibleRuntimes: ['nodejs14.x', 'nodejs16.x', 'nodejs18.x'], LicenseInfo: 'MIT-0', /* CompatibleArchitectures: [ 'x86_64', @@ -51,5 +45,4 @@ describe('Class: LayerPublisherStack', () => { Type: 'String', }); }); - -}); \ No newline at end of file +});