Skip to content

Commit 654d32e

Browse files
feat: add functions configuration API to @netlify/config (#2390)
* feat: add functions configuration API * chore: remove dead branch and rename variables * chore: improve validation of functions object * chore: update ESLint exception * chore: update property names * feat: add validation for functions.* * chore: rename fixtures * chore: update property names
1 parent 28983e5 commit 654d32e

File tree

38 files changed

+618
-5
lines changed

38 files changed

+618
-5
lines changed
Binary file not shown.

packages/build/tests/core/snapshots/tests.js.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ Generated by [AVA](https://ava.li).
8181
"commandOrigin": "config",␊
8282
"environment": {}␊
8383
},␊
84+
"functions": {},␊
8485
"plugins": []␊
8586
},␊
8687
"configPath": "/file/path",␊
15 Bytes
Binary file not shown.
-4 Bytes
Binary file not shown.
Binary file not shown.
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
'use strict'
2+
3+
const isPlainObj = require('is-plain-obj')
4+
5+
const configProperties = new Set(['external_node_modules', 'ignored_node_modules'])
6+
7+
const WILDCARD_ALL = '*'
8+
9+
const isConfigLeaf = (obj) => isPlainObj(obj) && Object.keys(obj).every(isConfigProperty)
10+
11+
const isConfigProperty = (prop) => configProperties.has(prop)
12+
13+
// Normalizes a functions configuration object, so that the first level of keys
14+
// represents function expressions mapped to a configuration object.
15+
//
16+
// Example input:
17+
// {
18+
// "external_node_modules": ["one"],
19+
// "my-function": { "external_node_modules": ["two"] }
20+
// }
21+
//
22+
// Example output:
23+
// {
24+
// "*": { "external_node_modules": ["one"] },
25+
// "my-function": { "external_node_modules": ["two"] }
26+
// }
27+
const normalize = (functions) => {
28+
const normalizedFunctions = Object.keys(functions).reduce((result, prop) => {
29+
// If `prop` is one of `configProperties``, we might be dealing with a
30+
// top-level property (i.e. one that targets all functions). We consider
31+
// that to be the case if the value is an object where all keys are also
32+
// config properties. If not, it's simply a function with the same name
33+
// as one of the config properties.
34+
if (isConfigProperty(prop) && !isConfigLeaf(functions[prop])) {
35+
return {
36+
...result,
37+
[WILDCARD_ALL]: {
38+
...result[WILDCARD_ALL],
39+
[prop]: functions[prop],
40+
},
41+
}
42+
}
43+
44+
return {
45+
...result,
46+
[prop]: functions[prop],
47+
}
48+
}, {})
49+
50+
return normalizedFunctions
51+
}
52+
53+
module.exports = { normalize }

packages/config/src/normalize.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
'use strict'
22

3+
const { normalize: normalizeFunctionsConfig } = require('./functions_config')
34
const { deepMerge } = require('./utils/merge')
45
const { removeFalsy } = require('./utils/remove_falsy')
56

67
// Normalize configuration object
78
const normalizeConfig = function (config) {
8-
const { build, plugins, ...configA } = deepMerge(DEFAULT_CONFIG, config)
9+
const { build, functions, plugins, ...configA } = deepMerge(DEFAULT_CONFIG, config)
10+
const functionsA = normalizeFunctionsConfig(functions)
911
const pluginsA = plugins.map(normalizePlugin)
10-
return { ...configA, build, plugins: pluginsA }
12+
return { ...configA, build, functions: functionsA, plugins: pluginsA }
1113
}
1214

1315
const DEFAULT_CONFIG = {
1416
build: { environment: {} },
17+
functions: {},
1518
plugins: [],
1619
}
1720

packages/config/src/validate/helpers.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
const { normalize } = require('path')
44

5+
const isPlainObj = require('is-plain-obj')
6+
7+
const isArrayOfObjects = function (value) {
8+
return Array.isArray(value) && value.every(isPlainObj)
9+
}
10+
11+
const isArrayOfStrings = function (value) {
12+
return Array.isArray(value) && value.every(isString)
13+
}
14+
515
const isString = function (value) {
616
return typeof value === 'string'
717
}
@@ -37,4 +47,4 @@ const removeParentDots = function (path) {
3747

3848
const PARENT_DOTS_REGEXP = /\.\.[/\\]/g
3949

40-
module.exports = { isString, validProperties, insideRootCheck, removeParentDots }
50+
module.exports = { isArrayOfObjects, isArrayOfStrings, isString, validProperties, insideRootCheck, removeParentDots }

packages/config/src/validate/validations.js

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
1+
/* eslint-disable max-lines */
12
'use strict'
23

34
const isPlainObj = require('is-plain-obj')
45
const validateNpmPackageName = require('validate-npm-package-name')
56

6-
const { isString, validProperties, insideRootCheck, removeParentDots } = require('./helpers')
7+
const {
8+
isArrayOfObjects,
9+
isArrayOfStrings,
10+
isString,
11+
validProperties,
12+
insideRootCheck,
13+
removeParentDots,
14+
} = require('./helpers')
715

816
// List of validations performed on the configuration file.
917
// Validation are performed in order: parent should be before children.
@@ -30,7 +38,7 @@ const PRE_CASE_NORMALIZE_VALIDATIONS = [
3038
const PRE_MERGE_VALIDATIONS = [
3139
{
3240
property: 'plugins',
33-
check: (value) => Array.isArray(value) && value.every(isPlainObj),
41+
check: isArrayOfObjects,
3442
message: 'must be an array of objects.',
3543
example: () => ({ plugins: [{ package: 'netlify-plugin-one' }, { package: 'netlify-plugin-two' }] }),
3644
},
@@ -60,6 +68,14 @@ const PRE_NORMALIZE_VALIDATIONS = [
6068
message: 'must be a string',
6169
example: () => ({ build: { command: 'npm run build' } }),
6270
},
71+
{
72+
property: 'functions',
73+
check: isPlainObj,
74+
message: 'must be an object.',
75+
example: () => ({
76+
functions: { external_node_modules: ['module-one', 'module-two'] },
77+
}),
78+
},
6379
]
6480

6581
const EXAMPLE_PORT = 80
@@ -149,6 +165,30 @@ const POST_NORMALIZE_VALIDATIONS = [
149165
...insideRootCheck,
150166
example: (edgeHandlers) => ({ build: { edge_handlers: removeParentDots(edgeHandlers) } }),
151167
},
168+
{
169+
property: 'functions.*',
170+
check: isPlainObj,
171+
message: 'must be an object.',
172+
example: (value, key, prevPath) => ({
173+
functions: { [prevPath[1]]: { external_node_modules: ['module-one', 'module-two'] } },
174+
}),
175+
},
176+
{
177+
property: 'functions.*.external_node_modules',
178+
check: isArrayOfStrings,
179+
message: 'must be an array of strings.',
180+
example: (value, key, prevPath) => ({
181+
functions: { [prevPath[1]]: { external_node_modules: ['module-one', 'module-two'] } },
182+
}),
183+
},
184+
{
185+
property: 'functions.*.ignored_node_modules',
186+
check: isArrayOfStrings,
187+
message: 'must be an array of strings.',
188+
example: (value, key, prevPath) => ({
189+
functions: { [prevPath[1]]: { ignored_node_modules: ['module-one', 'module-two'] } },
190+
}),
191+
},
152192
]
153193

154194
module.exports = {
@@ -158,3 +198,4 @@ module.exports = {
158198
PRE_NORMALIZE_VALIDATIONS,
159199
POST_NORMALIZE_VALIDATIONS,
160200
}
201+
/* eslint-enable max-lines */

packages/config/tests/api/snapshots/tests.js.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Generated by [AVA](https://ava.li).
1515
"build": {␊
1616
"environment": {}␊
1717
},␊
18+
"functions": {},␊
1819
"plugins": []␊
1920
},␊
2021
"configPath": "/file/path",␊
@@ -109,6 +110,7 @@ Generated by [AVA](https://ava.li).
109110
"build": {␊
110111
"environment": {}␊
111112
},␊
113+
"functions": {},␊
112114
"plugins": []␊
113115
},␊
114116
"configPath": "/file/path",␊
@@ -197,6 +199,7 @@ Generated by [AVA](https://ava.li).
197199
"build": {␊
198200
"environment": {}␊
199201
},␊
202+
"functions": {},␊
200203
"plugins": []␊
201204
},␊
202205
"configPath": "/file/path",␊
@@ -288,6 +291,7 @@ Generated by [AVA](https://ava.li).
288291
"build": {␊
289292
"environment": {}␊
290293
},␊
294+
"functions": {},␊
291295
"plugins": []␊
292296
},␊
293297
"configPath": "/file/path",␊
@@ -311,6 +315,7 @@ Generated by [AVA](https://ava.li).
311315
"build": {␊
312316
"environment": {}␊
313317
},␊
318+
"functions": {},␊
314319
"plugins": []␊
315320
},␊
316321
"configPath": "/file/path",␊
@@ -399,6 +404,7 @@ Generated by [AVA](https://ava.li).
399404
"build": {␊
400405
"environment": {}␊
401406
},␊
407+
"functions": {},␊
402408
"plugins": []␊
403409
},␊
404410
"configPath": "/file/path",␊
@@ -493,6 +499,7 @@ Generated by [AVA](https://ava.li).
493499
"build": {␊
494500
"environment": {}␊
495501
},␊
502+
"functions": {},␊
496503
"plugins": []␊
497504
},␊
498505
"configPath": "/file/path",␊
@@ -611,6 +618,7 @@ Generated by [AVA](https://ava.li).
611618
"functions": "/file/path",␊
612619
"publish": "/file/path"␊
613620
},␊
621+
"functions": {},␊
614622
"plugins": [␊
615623
{␊
616624
"inputs": {␊
@@ -761,6 +769,7 @@ Generated by [AVA](https://ava.li).
761769
"build": {␊
762770
"environment": {}␊
763771
},␊
772+
"functions": {},␊
764773
"plugins": []␊
765774
},␊
766775
"configPath": "/file/path",␊
@@ -784,6 +793,7 @@ Generated by [AVA](https://ava.li).
784793
"build": {␊
785794
"environment": {}␊
786795
},␊
796+
"functions": {},␊
787797
"plugins": []␊
788798
},␊
789799
"configPath": "/file/path",␊
@@ -872,6 +882,7 @@ Generated by [AVA](https://ava.li).
872882
"build": {␊
873883
"environment": {}␊
874884
},␊
885+
"functions": {},␊
875886
"plugins": []␊
876887
},␊
877888
"configPath": "/file/path",␊
@@ -966,6 +977,7 @@ Generated by [AVA](https://ava.li).
966977
"build": {␊
967978
"environment": {}␊
968979
},␊
980+
"functions": {},␊
969981
"plugins": []␊
970982
},␊
971983
"configPath": "/file/path",␊
@@ -1061,6 +1073,7 @@ Generated by [AVA](https://ava.li).
10611073
"build": {␊
10621074
"environment": {}␊
10631075
},␊
1076+
"functions": {},␊
10641077
"plugins": []␊
10651078
},␊
10661079
"configPath": "/file/path",␊
@@ -1180,6 +1193,7 @@ Generated by [AVA](https://ava.li).
11801193
"build": {␊
11811194
"environment": {}␊
11821195
},␊
1196+
"functions": {},␊
11831197
"plugins": []␊
11841198
},␊
11851199
"configPath": "/file/path",␊
@@ -1268,6 +1282,7 @@ Generated by [AVA](https://ava.li).
12681282
"build": {␊
12691283
"environment": {}␊
12701284
},␊
1285+
"functions": {},␊
12711286
"plugins": []␊
12721287
},␊
12731288
"configPath": "/file/path",␊
@@ -1367,6 +1382,7 @@ Generated by [AVA](https://ava.li).
13671382
"functions": "/file/path",␊
13681383
"publish": "/file/path"␊
13691384
},␊
1385+
"functions": {},␊
13701386
"plugins": [␊
13711387
{␊
13721388
"inputs": {␊
@@ -1512,6 +1528,7 @@ Generated by [AVA](https://ava.li).
15121528
"environment": {},␊
15131529
"publish": "/file/path"␊
15141530
},␊
1531+
"functions": {},␊
15151532
"plugins": []␊
15161533
},␊
15171534
"configPath": "/file/path",␊
-20 Bytes
Binary file not shown.

packages/config/tests/base/snapshots/tests.js.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ Generated by [AVA](https://ava.li).
2727
"functions": "/file/path",␊
2828
"publish": "/file/path"␊
2929
},␊
30+
"functions": {},␊
3031
"plugins": []␊
3132
},␊
3233
"configPath": "/file/path",␊
@@ -116,6 +117,7 @@ Generated by [AVA](https://ava.li).
116117
"commandOrigin": "config",␊
117118
"environment": {}␊
118119
},␊
120+
"functions": {},␊
119121
"plugins": []␊
120122
},␊
121123
"configPath": "/file/path",␊
@@ -205,6 +207,7 @@ Generated by [AVA](https://ava.li).
205207
"commandOrigin": "config",␊
206208
"environment": {}␊
207209
},␊
210+
"functions": {},␊
208211
"plugins": []␊
209212
},␊
210213
"configPath": "/file/path",␊
@@ -297,6 +300,7 @@ Generated by [AVA](https://ava.li).
297300
"functions": "/file/path",␊
298301
"publish": "/file/path"␊
299302
},␊
303+
"functions": {},␊
300304
"plugins": []␊
301305
},␊
302306
"configPath": "/file/path",␊
Binary file not shown.

packages/config/tests/cli/snapshots/tests.js.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ Generated by [AVA](https://ava.li).
6060
"build": {␊
6161
"environment": {}␊
6262
},␊
63+
"functions": {},␊
6364
"plugins": []␊
6465
},␊
6566
"configPath": "/file/path",␊
@@ -216,6 +217,7 @@ Generated by [AVA](https://ava.li).
216217
"configPath": "/file/path",␊
217218
"buildDir": "/file/path",␊
218219
"config": {␊
220+
"functions": {},␊
219221
"plugins": [],␊
220222
"build": {␊
221223
"environment": {}␊
@@ -240,6 +242,7 @@ Generated by [AVA](https://ava.li).
240242
"build": {␊
241243
"environment": {}␊
242244
},␊
245+
"functions": {},␊
243246
"plugins": []␊
244247
},␊
245248
"configPath": "/file/path",␊
@@ -330,6 +333,7 @@ Generated by [AVA](https://ava.li).
330333
"build": {␊
331334
"environment": {}␊
332335
},␊
336+
"functions": {},␊
333337
"plugins": []␊
334338
},␊
335339
"configPath": "/file/path",␊
Binary file not shown.

0 commit comments

Comments
 (0)