serverless-plugin-warmup 8.2.1 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  Keep your lambdas warm during winter.
10
10
 
11
11
  **Requirements:**
12
- * Node *v14.x* or higher
12
+ * Node *v20.x* or higher
13
13
  * Serverless *v3.8* or higher
14
14
  * AWS provider
15
15
 
@@ -64,7 +64,7 @@ The options are the same for all the warmers:
64
64
  * **cleanFolder** Whether to automatically delete the generated code folder. You might want to keep it if you are doing some custom packaging (defaults to `true`)
65
65
  * **name** Name of the generated warmer lambda (defaults to `${service}-${stage}-warmup-plugin-${warmerName}`)
66
66
  * **roleName** Name to be applied to the default warmer lambda role. Ignored if a the role setting is used (defaults to `${service.service}-${stage}-${region}-${warmerName.toLowerCase()}-role`)
67
- * **role** Role to apply to the warmer lambda (defaults to the role in the provider)
67
+ * **role** Role to apply to the warmer lambda (defaults to a custom role with minimal [permissions](#permissions))
68
68
  * **tags** Tag to apply to the generated warmer lambda (defaults to the serverless default tags)
69
69
  * **vpc** The VPC and subnets in which to deploy. Can be any [Serverless VPC configuration](https://serverless.com/framework/docs/providers/aws/guide/functions#vpc-configuration) or be set to `false` in order to deploy the warmup function outside of a VPC (defaults to the vpc in the provider)
70
70
  * **memorySize** The memory to be assigned to the warmer lambda (defaults to `128`)
@@ -258,7 +258,7 @@ The permissions can also be added to all lambdas using setting the role to `IamR
258
258
  ```yaml
259
259
  provider:
260
260
  name: aws
261
- runtime: nodejs18.x
261
+ runtime: nodejs24.x
262
262
  iamRoleStatements:
263
263
  - Effect: 'Allow'
264
264
  Action:
@@ -585,13 +585,13 @@ You can check the Lambda [pricing](https://aws.amazon.com/lambda/pricing/) and C
585
585
 
586
586
  #### Example
587
587
 
588
- If you have a single warmer and want to warm 10 functions, each with `memorySize = 1024` and `duration = 10`, using the default settings (and we ignore the free tier):
588
+ If you have a single warmer and want to warm 10 functions, each with `memorySize = 1024` and `duration = 10`, using the default settings ($0.0000166667 for every GB-second) and ignoring the free tier:
589
589
 
590
- * WarmUp: runs 8640 times per month = $0.18
591
- * 10 warm lambdas: each invoked 8640 times per month = $14.4
592
- * Total = $14.58
590
+ * WarmUp: runs 8640 times per month = $0.0
591
+ * 10 warm lambdas: each invoked 8640 times per month = $0.3
592
+ * Total = $0.3
593
593
 
594
- CloudWatch costs are not in this example because they are very low.
594
+ CloudWatch costs are not consdiered in this example.
595
595
 
596
596
  ## Contribute
597
597
 
package/biome.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json",
3
+ "vcs": {
4
+ "enabled": true,
5
+ "clientKind": "git",
6
+ "useIgnoreFile": true
7
+ },
8
+ "files": {
9
+ "includes": ["**", "!!**/coverage", "!!**/test_integration", "!!**/node_modules"]
10
+ },
11
+ "formatter": {
12
+ "indentWidth": 2,
13
+ "lineWidth": 100
14
+ },
15
+ "linter": {
16
+ "enabled": true,
17
+ "rules": {
18
+ "recommended": true,
19
+ "style": {
20
+ "useNodejsImportProtocol": "off"
21
+ },
22
+ "suspicious": {
23
+ "noEmptyBlockStatements": "off",
24
+ "noAssignInExpressions": "off"
25
+ }
26
+ }
27
+ },
28
+ "javascript": {
29
+ "formatter": {
30
+ "quoteStyle": "single",
31
+ "trailingCommas": "all",
32
+ "semicolons": "always",
33
+ "arrowParentheses": "always"
34
+ },
35
+ "globals": [
36
+ "jest",
37
+ "describe",
38
+ "it",
39
+ "expect",
40
+ "beforeEach",
41
+ "afterEach",
42
+ "beforeAll",
43
+ "afterAll",
44
+ "test"
45
+ ]
46
+ }
47
+ }
package/package.json CHANGED
@@ -1,40 +1,38 @@
1
1
  {
2
- "name": "serverless-plugin-warmup",
3
- "version": "8.2.1",
4
- "description": "Keep your lambdas warm during winter.",
5
- "main": "src/index.js",
6
- "scripts": {
7
- "prepare": "husky install",
8
- "lint": "eslint .",
9
- "lint:fix": "eslint --fix .",
10
- "test": "jest test",
11
- "test-with-coverage": "jest --coverage --collectCoverageFrom=src/**/*.js test"
12
- },
13
- "author": "Juanjo Diaz <juanjo.diazmo@gmail.com>",
14
- "license": "MIT",
15
- "repository": {
16
- "type": "git",
17
- "url": "git+https://github.com/juanjoDiaz/serverless-plugin-warmup.git"
18
- },
19
- "keywords": [
20
- "serverless plugin warm up lambdas",
21
- "serverless warmup",
22
- "serverless plugin",
23
- "lambda serverless",
24
- "warm lambda",
25
- "lambda coldstart",
26
- "lambda cold"
27
- ],
28
- "bugs": {
29
- "url": "https://github.com/juanjoDiaz/serverless-plugin-warmup/issues"
30
- },
31
- "homepage": "https://github.com/juanjoDiaz/serverless-plugin-warmup",
32
- "dependencies": {},
33
- "devDependencies": {
34
- "eslint": "^8.5.0",
35
- "eslint-config-airbnb-base": "^15.0.0",
36
- "eslint-plugin-import": "^2.24.2",
37
- "husky": "^8.0.1",
38
- "jest": "^29.1.2"
39
- }
2
+ "name": "serverless-plugin-warmup",
3
+ "version": "8.4.0",
4
+ "description": "Keep your lambdas warm during winter.",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "prepare": "husky",
8
+ "lint": "biome check .",
9
+ "lint:fix": "biome check --write .",
10
+ "format": "biome format --write .",
11
+ "test": "jest test",
12
+ "test-with-coverage": "jest --coverage --collectCoverageFrom=src/**/*.js test"
13
+ },
14
+ "author": "Juanjo Diaz <juanjo.diazmo@gmail.com>",
15
+ "license": "MIT",
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/juanjoDiaz/serverless-plugin-warmup.git"
19
+ },
20
+ "keywords": [
21
+ "serverless plugin warm up lambdas",
22
+ "serverless warmup",
23
+ "serverless plugin",
24
+ "lambda serverless",
25
+ "warm lambda",
26
+ "lambda coldstart",
27
+ "lambda cold"
28
+ ],
29
+ "bugs": {
30
+ "url": "https://github.com/juanjoDiaz/serverless-plugin-warmup/issues"
31
+ },
32
+ "homepage": "https://github.com/juanjoDiaz/serverless-plugin-warmup",
33
+ "devDependencies": {
34
+ "@biomejs/biome": "2.3.11",
35
+ "husky": "^9.1.7",
36
+ "jest": "^30.2.0"
37
+ }
40
38
  }
package/src/config.js CHANGED
@@ -7,51 +7,60 @@ const path = require('path');
7
7
  * @return {Object} - Global configuration options
8
8
  * */
9
9
  function getWarmerConfig(config, defaultOpts) {
10
- const folderName = (typeof config.folderName === 'string') ? config.folderName : defaultOpts.folderName;
10
+ const folderName =
11
+ typeof config.folderName === 'string' ? config.folderName : defaultOpts.folderName;
11
12
 
12
- /* eslint-disable no-nested-ternary */
13
- return {
14
- folderName,
15
- pathHandler: path.join(folderName, 'index.warmUp'),
16
- cleanFolder: (typeof config.cleanFolder === 'boolean') ? config.cleanFolder : defaultOpts.cleanFolder,
17
- name: (config.name !== undefined) ? config.name : defaultOpts.name,
18
- roleName: (config.roleName !== undefined) ? config.roleName : defaultOpts.roleName,
19
- role: (config.role !== undefined) ? config.role : defaultOpts.role,
20
- tags: (config.tags !== undefined) ? config.tags : defaultOpts.tags,
21
- vpc: config.vpc === false ? { securityGroupIds: [], subnetIds: [] }
22
- : (config.vpc !== undefined ? config.vpc : defaultOpts.vpc),
23
- events: (Array.isArray(config.events)) ? config.events : defaultOpts.events,
24
- architecture: (config.architecture !== undefined)
25
- ? config.architecture
26
- : defaultOpts.architecture,
27
- package: typeof config.package === 'object'
28
- ? {
29
- individually: (config.package.individually !== undefined)
30
- ? config.package.individually
31
- : defaultOpts.package.individually,
32
- patterns: ['!**', ...Array.isArray(config.package.patterns)
33
- ? (config.package.patterns.includes(path.join(folderName, '**'))
34
- ? config.package.patterns
35
- : [...config.package.patterns, path.join(folderName, '**')])
36
- : [...defaultOpts.package.patterns, path.join(folderName, '**')]],
37
- }
38
- : {
39
- ...defaultOpts.package,
40
- patterns: ['!**', ...defaultOpts.package.patterns, path.join(folderName, '**')],
41
- },
42
- memorySize: (config.memorySize !== undefined) ? config.memorySize : defaultOpts.memorySize,
43
- timeout: (config.timeout !== undefined) ? config.timeout : defaultOpts.timeout,
44
- environment: (config.environment !== undefined)
45
- ? config.environment
46
- : defaultOpts.environment,
47
- tracing: (config.tracing !== undefined) ? config.tracing : defaultOpts.tracing,
48
- verbose: (config.verbose !== undefined) ? config.verbose : defaultOpts.verbose,
49
- logRetentionInDays: (config.logRetentionInDays !== undefined)
50
- ? config.logRetentionInDays
51
- : defaultOpts.logRetentionInDays,
52
- prewarm: (config.prewarm !== undefined) ? config.prewarm : defaultOpts.prewarm,
53
- };
54
- /* eslint-enable no-nested-ternary */
13
+ /* eslint-disable no-nested-ternary */
14
+ return {
15
+ folderName,
16
+ pathHandler: path.join(folderName, 'index.warmUp'),
17
+ cleanFolder:
18
+ typeof config.cleanFolder === 'boolean' ? config.cleanFolder : defaultOpts.cleanFolder,
19
+ name: config.name !== undefined ? config.name : defaultOpts.name,
20
+ roleName: config.roleName !== undefined ? config.roleName : defaultOpts.roleName,
21
+ role: config.role !== undefined ? config.role : defaultOpts.role,
22
+ tags: config.tags !== undefined ? config.tags : defaultOpts.tags,
23
+ vpc:
24
+ config.vpc === false
25
+ ? { securityGroupIds: [], subnetIds: [] }
26
+ : config.vpc !== undefined
27
+ ? config.vpc
28
+ : defaultOpts.vpc,
29
+ events: Array.isArray(config.events) ? config.events : defaultOpts.events,
30
+ architecture:
31
+ config.architecture !== undefined ? config.architecture : defaultOpts.architecture,
32
+ package:
33
+ typeof config.package === 'object'
34
+ ? {
35
+ individually:
36
+ config.package.individually !== undefined
37
+ ? config.package.individually
38
+ : defaultOpts.package.individually,
39
+ patterns: [
40
+ '!**',
41
+ ...(Array.isArray(config.package.patterns)
42
+ ? config.package.patterns.includes(path.join(folderName, '**'))
43
+ ? config.package.patterns
44
+ : [...config.package.patterns, path.join(folderName, '**')]
45
+ : [...defaultOpts.package.patterns, path.join(folderName, '**')]),
46
+ ],
47
+ }
48
+ : {
49
+ ...defaultOpts.package,
50
+ patterns: ['!**', ...defaultOpts.package.patterns, path.join(folderName, '**')],
51
+ },
52
+ memorySize: config.memorySize !== undefined ? config.memorySize : defaultOpts.memorySize,
53
+ timeout: config.timeout !== undefined ? config.timeout : defaultOpts.timeout,
54
+ environment: config.environment !== undefined ? config.environment : defaultOpts.environment,
55
+ tracing: config.tracing !== undefined ? config.tracing : defaultOpts.tracing,
56
+ verbose: config.verbose !== undefined ? config.verbose : defaultOpts.verbose,
57
+ logRetentionInDays:
58
+ config.logRetentionInDays !== undefined
59
+ ? config.logRetentionInDays
60
+ : defaultOpts.logRetentionInDays,
61
+ prewarm: config.prewarm !== undefined ? config.prewarm : defaultOpts.prewarm,
62
+ };
63
+ /* eslint-enable no-nested-ternary */
55
64
  }
56
65
 
57
66
  /**
@@ -61,25 +70,23 @@ function getWarmerConfig(config, defaultOpts) {
61
70
  * @return {Object} - Function-specific configuration options
62
71
  * */
63
72
  function getFunctionConfig(config, defaultOpts) {
64
- /* eslint-disable no-nested-ternary */
65
- return {
66
- enabled: (config.enabled !== undefined)
67
- ? config.enabled
68
- : defaultOpts.enabled,
69
- alias: (config.alias !== undefined)
70
- ? config.alias
71
- : defaultOpts.alias,
72
- clientContext: (config.clientContext !== undefined)
73
- ? config.clientContext && JSON.stringify(config.clientContext)
74
- : defaultOpts.clientContext,
75
- payload: (config.payload !== undefined)
76
- ? (config.payloadRaw ? config.payload : JSON.stringify(config.payload))
77
- : defaultOpts.payload,
78
- concurrency: (config.concurrency !== undefined)
79
- ? config.concurrency
80
- : defaultOpts.concurrency,
81
- };
82
- /* eslint-enable no-nested-ternary */
73
+ /* eslint-disable no-nested-ternary */
74
+ return {
75
+ enabled: config.enabled !== undefined ? config.enabled : defaultOpts.enabled,
76
+ alias: config.alias !== undefined ? config.alias : defaultOpts.alias,
77
+ clientContext:
78
+ config.clientContext !== undefined
79
+ ? config.clientContext && JSON.stringify(config.clientContext)
80
+ : defaultOpts.clientContext,
81
+ payload:
82
+ config.payload !== undefined
83
+ ? config.payloadRaw
84
+ ? config.payload
85
+ : JSON.stringify(config.payload)
86
+ : defaultOpts.payload,
87
+ concurrency: config.concurrency !== undefined ? config.concurrency : defaultOpts.concurrency,
88
+ };
89
+ /* eslint-enable no-nested-ternary */
83
90
  }
84
91
 
85
92
  /**
@@ -88,53 +95,54 @@ function getFunctionConfig(config, defaultOpts) {
88
95
  * @return {Array} - List of functions to be warmed up and their specific configs
89
96
  * */
90
97
  function getFunctionsByWarmer(service, stage, configsByWarmer, serverlessClasses) {
91
- const functions = service.getAllFunctions()
92
- .map((name) => service.getFunction(name))
93
- .map((config) => {
94
- if (config.warmup === undefined) {
95
- return {
96
- name: config.name,
97
- config: Object.entries(configsByWarmer)
98
- .reduce((warmers, [warmerName, warmerConfig]) => ({
99
- ...warmers,
100
- [warmerName]: getFunctionConfig({}, warmerConfig),
101
- }), {}),
102
- };
103
- }
98
+ const functions = service
99
+ .getAllFunctions()
100
+ .map((name) => service.getFunction(name))
101
+ .map((config) => {
102
+ if (config.warmup === undefined) {
103
+ return {
104
+ name: config.name,
105
+ config: Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
106
+ warmers[warmerName] = getFunctionConfig({}, warmerConfig);
107
+ return warmers;
108
+ }, {}),
109
+ };
110
+ }
111
+ const unknownWarmers = Object.keys(config.warmup).filter(
112
+ (warmerName) => configsByWarmer[warmerName] === undefined,
113
+ );
114
+ if (unknownWarmers.length > 0) {
115
+ throw new serverlessClasses.Error(
116
+ `WarmUp: Invalid function-level warmup configuration (${unknownWarmers.join(', ')}) in function ${config.name}. Every warmer should be declared in the custom section.`,
117
+ );
118
+ }
104
119
 
105
- const unknownWarmers = Object.keys(config.warmup)
106
- .filter((warmerName) => configsByWarmer[warmerName] === undefined);
107
- if (unknownWarmers.length > 0) {
108
- throw new serverlessClasses.Error(`WarmUp: Invalid function-level warmup configuration (${unknownWarmers.join(', ')}) in function ${config.name}. Every warmer should be declared in the custom section.`);
109
- }
120
+ return {
121
+ name: config.name,
122
+ config: Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
123
+ warmers[warmerName] = getFunctionConfig(config.warmup[warmerName] || {}, warmerConfig);
124
+ return warmers;
125
+ }, {}),
126
+ };
127
+ });
128
+ function isEnabled(enabled) {
129
+ return (
130
+ enabled === true ||
131
+ enabled === 'true' ||
132
+ enabled === stage ||
133
+ (Array.isArray(enabled) && enabled.indexOf(stage) !== -1)
134
+ );
135
+ }
110
136
 
111
- return {
112
- name: config.name,
113
- config: Object.entries(configsByWarmer)
114
- .reduce((warmers, [warmerName, warmerConfig]) => ({
115
- ...warmers,
116
- [warmerName]: getFunctionConfig(config.warmup[warmerName] || {}, warmerConfig),
117
- }), {}),
118
- };
119
- });
120
-
121
- function isEnabled(enabled) {
122
- return enabled === true
123
- || enabled === 'true'
124
- || enabled === stage
125
- || (Array.isArray(enabled) && enabled.indexOf(stage) !== -1);
126
- }
127
-
128
- return functions.reduce((warmersAcc, fn) => {
129
- Object.entries(fn.config)
130
- .forEach(([warmerName, config]) => {
131
- if (!isEnabled(config.enabled)) return;
132
- // eslint-disable-next-line no-param-reassign
133
- if (!warmersAcc[warmerName]) warmersAcc[warmerName] = [];
134
- warmersAcc[warmerName].push({ name: fn.name, config });
135
- });
136
- return warmersAcc;
137
- }, {});
137
+ return functions.reduce((warmersAcc, fn) => {
138
+ Object.entries(fn.config).forEach(([warmerName, config]) => {
139
+ if (!isEnabled(config.enabled)) return;
140
+ // eslint-disable-next-line no-param-reassign
141
+ if (!warmersAcc[warmerName]) warmersAcc[warmerName] = [];
142
+ warmersAcc[warmerName].push({ name: fn.name, config });
143
+ });
144
+ return warmersAcc;
145
+ }, {});
138
146
  }
139
147
 
140
148
  /**
@@ -143,50 +151,54 @@ function getFunctionsByWarmer(service, stage, configsByWarmer, serverlessClasses
143
151
  * @return {Object} - Configuration options to be used by the plugin
144
152
  * */
145
153
  function getConfigsByWarmer({ service, classes }, stage) {
146
- const getWarmerDefaultOpts = (warmerName) => ({
147
- folderName: path.join('.warmup', warmerName),
148
- cleanFolder: true,
149
- memorySize: 128,
150
- name: `${service.service}-${stage}-warmup-plugin-${warmerName}`,
151
- events: [{ schedule: 'rate(5 minutes)' }],
152
- package: {
153
- individually: true,
154
- patterns: [],
155
- },
156
- timeout: 10,
157
- environment: Object.keys(service.provider.environment || [])
158
- .reduce((obj, k) => ({ ...obj, [k]: undefined }), {}),
159
- verbose: true,
160
- prewarm: false,
161
- });
154
+ const getWarmerDefaultOpts = (warmerName) => ({
155
+ folderName: path.join('.warmup', warmerName),
156
+ cleanFolder: true,
157
+ memorySize: 128,
158
+ name: `${service.service}-${stage}-warmup-plugin-${warmerName}`,
159
+ events: [{ schedule: 'rate(5 minutes)' }],
160
+ package: {
161
+ individually: true,
162
+ patterns: [],
163
+ },
164
+ timeout: 10,
165
+ environment: Object.keys(service.provider.environment || []).reduce((obj, k) => {
166
+ obj[k] = undefined;
167
+ return obj;
168
+ }, {}),
169
+ verbose: true,
170
+ prewarm: false,
171
+ });
162
172
 
163
- const functionDefaultOpts = {
164
- enabled: false,
165
- clientContext: undefined,
166
- payload: JSON.stringify({ source: 'serverless-plugin-warmup' }),
167
- concurrency: 1,
168
- };
173
+ const functionDefaultOpts = {
174
+ enabled: false,
175
+ clientContext: undefined,
176
+ payload: JSON.stringify({ source: 'serverless-plugin-warmup' }),
177
+ concurrency: 1,
178
+ };
169
179
 
170
- const configsByWarmer = Object.entries(service.custom ? service.custom.warmup : {})
171
- .reduce((warmers, [warmerName, warmerConfig]) => ({
172
- ...warmers,
173
- [warmerName]: {
174
- ...getWarmerConfig(warmerConfig, getWarmerDefaultOpts(warmerName)),
175
- ...getFunctionConfig(warmerConfig, functionDefaultOpts),
176
- },
177
- }), {});
180
+ const configsByWarmer = Object.entries(service.custom?.warmup || {}).reduce(
181
+ (warmers, [warmerName, warmerConfig]) => {
182
+ warmers[warmerName] = {
183
+ ...getWarmerConfig(warmerConfig, getWarmerDefaultOpts(warmerName)),
184
+ ...getFunctionConfig(warmerConfig, functionDefaultOpts),
185
+ };
186
+ return warmers;
187
+ },
188
+ {},
189
+ );
178
190
 
179
- const functionsByWarmer = getFunctionsByWarmer(service, stage, configsByWarmer, classes);
191
+ const functionsByWarmer = getFunctionsByWarmer(service, stage, configsByWarmer, classes);
180
192
 
181
- return Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => ({
182
- ...warmers,
183
- [warmerName]: {
184
- ...warmerConfig,
185
- functions: functionsByWarmer[warmerName] || [],
186
- },
187
- }), {});
193
+ return Object.entries(configsByWarmer).reduce((warmers, [warmerName, warmerConfig]) => {
194
+ warmers[warmerName] = {
195
+ ...warmerConfig,
196
+ functions: functionsByWarmer[warmerName] || [],
197
+ };
198
+ return warmers;
199
+ }, {});
188
200
  }
189
201
 
190
202
  module.exports = {
191
- getConfigsByWarmer,
203
+ getConfigsByWarmer,
192
204
  };