@architect/inventory 2.1.1 → 2.2.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/changelog.md +32 -0
- package/package.json +12 -5
- package/readme.md +1 -1
- package/src/config/pragmas/index.js +4 -1
- package/src/config/pragmas/indexes.js +6 -37
- package/src/config/pragmas/populate-lambda/{_streams.js → _tables-streams.js} +11 -5
- package/src/config/pragmas/populate-lambda/get-runtime.js +5 -0
- package/src/config/pragmas/populate-lambda/index.js +6 -4
- package/src/config/pragmas/tables-indexes.js +59 -0
- package/src/config/pragmas/{streams.js → tables-streams.js} +11 -11
- package/src/config/pragmas/validate/_scheduled.js +1 -6
- package/src/config/pragmas/validate/_tables-streams.js +19 -0
- package/src/config/pragmas/validate/_tables.js +1 -1
- package/src/config/pragmas/validate/index.js +12 -12
- package/src/defaults/index.js +2 -1
- package/src/get.js +3 -2
- package/src/lib/is.js +2 -1
- package/src/lib/pragmas.js +4 -3
- package/src/validate/config.js +64 -0
- package/src/validate/index.js +4 -6
- package/src/validate/layers.js +65 -33
- package/src/validate/tables-children.js +4 -4
- package/src/config/pragmas/validate/_streams.js +0 -19
- package/src/validate/arn.js +0 -24
- package/src/validate/runtimes.js +0 -28
package/changelog.md
CHANGED
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [2.2.0] 2021-11-16
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Finally formalized `@tables-streams`, the fully customizable successor to `@tables` with `stream true`
|
|
10
|
+
- Added `@tables-indexes` pragma
|
|
11
|
+
- `@tables-indexes` has identical semantics as (and will eventually supersede) `@indexes`
|
|
12
|
+
- Until Arc 10.0 + Inventory 3.0, consumers should now check both `inv.indexes` AND `inv.tables-indexes`
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## [2.1.3] 2021-11-04
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Hardened runtime validation by ensuring non-string values will fail gracefully
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## [2.1.2] 2021-10-28
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- Added memory / timeout configuration validation
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Improved layer validation error formatting
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
5
37
|
## [2.1.1] 2021-10-13
|
|
6
38
|
|
|
7
39
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@architect/inventory",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Architect project resource enumeration utility",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,21 +23,28 @@
|
|
|
23
23
|
"@architect/asap": "~4.1.0",
|
|
24
24
|
"@architect/parser": "~5.0.2",
|
|
25
25
|
"@architect/utils": "~3.0.4",
|
|
26
|
-
"lambda-runtimes": "~1.0
|
|
26
|
+
"lambda-runtimes": "~1.1.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@architect/eslint-config": "~2.0.1",
|
|
30
30
|
"aws-sdk": "2.880.0",
|
|
31
31
|
"aws-sdk-mock": "~5.4.0",
|
|
32
32
|
"cross-env": "~7.0.3",
|
|
33
|
-
"eslint": "~8.0
|
|
34
|
-
"mock-fs": "~5.1.
|
|
33
|
+
"eslint": "~8.2.0",
|
|
34
|
+
"mock-fs": "~5.1.2",
|
|
35
35
|
"mock-require": "~3.0.3",
|
|
36
36
|
"nyc": "~15.1.0",
|
|
37
37
|
"tap-spec": "^5.0.0",
|
|
38
|
-
"tape": "^5.3.
|
|
38
|
+
"tape": "^5.3.2"
|
|
39
39
|
},
|
|
40
40
|
"eslintConfig": {
|
|
41
41
|
"extends": "@architect/eslint-config"
|
|
42
|
+
},
|
|
43
|
+
"nyc": {
|
|
44
|
+
"check-coverage": true,
|
|
45
|
+
"branches": 100,
|
|
46
|
+
"lines": 100,
|
|
47
|
+
"functions": 100,
|
|
48
|
+
"statements": 100
|
|
42
49
|
}
|
|
43
50
|
}
|
package/readme.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
[<img src="https://
|
|
1
|
+
[<img src="https://assets.arc.codes/architect-logo-500b@2x.png" width=500>](https://www.npmjs.com/package/@architect/inventory)
|
|
2
2
|
|
|
3
3
|
## [`@architect/inventory`](https://www.npmjs.com/package/@architect/inventory)
|
|
4
4
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
let { all: allPragmas } = require('../../lib/pragmas')
|
|
2
2
|
|
|
3
3
|
// Get all pragmas except special cases
|
|
4
|
-
let isSpecial = p =>
|
|
4
|
+
let isSpecial = p => [ 'shared', 'views' ].includes(p)
|
|
5
5
|
let visitors = allPragmas.map(p => {
|
|
6
6
|
// eslint-disable-next-line
|
|
7
7
|
if (!isSpecial(p)) return require(`./${p}`)
|
|
@@ -21,6 +21,9 @@ module.exports = function configureArcPragmas ({ arc, inventory }, errors) {
|
|
|
21
21
|
visitors.forEach(visitor => {
|
|
22
22
|
// Expects pragma visitors to have function name of: `configure${pragma}`
|
|
23
23
|
let name = visitor.name.replace('configure', '').toLowerCase()
|
|
24
|
+
// Special cases for dasherization
|
|
25
|
+
if (name === 'tablesindexes') name = 'tables-indexes'
|
|
26
|
+
if (name === 'tablesstreams') name = 'tables-streams'
|
|
24
27
|
pragmas[name] = visitor({ arc, inventory, errors })
|
|
25
28
|
})
|
|
26
29
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
let is = require('../../lib/is')
|
|
2
1
|
let validate = require('./validate')
|
|
2
|
+
let { getIndexes } = require('./tables-indexes')
|
|
3
3
|
|
|
4
4
|
module.exports = function configureIndexes ({ arc, errors }) {
|
|
5
5
|
if (!arc.indexes || !arc.indexes.length) return null
|
|
@@ -7,43 +7,12 @@ module.exports = function configureIndexes ({ arc, errors }) {
|
|
|
7
7
|
errors.push(`Specifying @indexes requires specifying corresponding @tables`)
|
|
8
8
|
return null
|
|
9
9
|
}
|
|
10
|
+
if (arc['tables-indexes']?.length && arc.indexes?.length) {
|
|
11
|
+
errors.push(`Either @tables-indexes or @indexes can be specified, but not both`)
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
10
14
|
|
|
11
|
-
let
|
|
12
|
-
function error (item) { errors.push(`Invalid @indexes item: ${item}`) }
|
|
13
|
-
|
|
14
|
-
let indexes = arc.indexes.map(index => {
|
|
15
|
-
if (is.object(index)) {
|
|
16
|
-
let name = Object.keys(index)[0]
|
|
17
|
-
let partitionKey = null
|
|
18
|
-
let partitionKeyType = null
|
|
19
|
-
let sortKey = null
|
|
20
|
-
let sortKeyType = null
|
|
21
|
-
let indexName = null
|
|
22
|
-
Object.entries(index[name]).forEach(([ key, value ]) => {
|
|
23
|
-
if (is.sortKey(value)) {
|
|
24
|
-
sortKey = key
|
|
25
|
-
sortKeyType = value.replace('**', '')
|
|
26
|
-
}
|
|
27
|
-
else if (is.primaryKey(value)) {
|
|
28
|
-
partitionKey = key
|
|
29
|
-
partitionKeyType = value.replace('*', '')
|
|
30
|
-
}
|
|
31
|
-
else if (isCustomName(key)) {
|
|
32
|
-
indexName = value
|
|
33
|
-
}
|
|
34
|
-
})
|
|
35
|
-
return {
|
|
36
|
-
indexName,
|
|
37
|
-
name,
|
|
38
|
-
partitionKey,
|
|
39
|
-
partitionKeyType,
|
|
40
|
-
sortKey,
|
|
41
|
-
sortKeyType,
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
error(index)
|
|
45
|
-
}).filter(Boolean) // Invalid indexes may create undefined entries in the map
|
|
46
|
-
|
|
15
|
+
let indexes = getIndexes(arc, 'indexes', errors)
|
|
47
16
|
validate.indexes(indexes, '@indexes', errors)
|
|
48
17
|
|
|
49
18
|
return indexes
|
|
@@ -2,23 +2,29 @@ let { join } = require('path')
|
|
|
2
2
|
let { existsSync } = require('fs')
|
|
3
3
|
let is = require('../../../lib/is')
|
|
4
4
|
|
|
5
|
-
module.exports = function
|
|
5
|
+
module.exports = function populateTablesStreams ({ type, item, dir, cwd, errors }) {
|
|
6
6
|
if (type === 'tables' && is.object(item)) {
|
|
7
7
|
let name = Object.keys(item)[0]
|
|
8
|
-
// Check for the legacy dir from before `@tables tablename stream true` generated
|
|
8
|
+
// Check for the legacy dir from before `@tables tablename stream true` generated a @tables-streams item
|
|
9
9
|
let legacySrc = join(cwd, dir, name)
|
|
10
10
|
let streamSrc = join(cwd, 'src', 'streams', name)
|
|
11
|
-
let
|
|
11
|
+
let tablesStreamsSrc = join(cwd, 'src', 'tables-streams', name)
|
|
12
|
+
|
|
13
|
+
let src
|
|
14
|
+
if (existsSync(legacySrc)) src = legacySrc
|
|
15
|
+
else if (existsSync(streamSrc)) src = streamSrc // TODO [remove] in 10.0
|
|
16
|
+
else src = tablesStreamsSrc
|
|
17
|
+
|
|
12
18
|
let table = name
|
|
13
19
|
return { name, src, table }
|
|
14
20
|
}
|
|
15
|
-
else if (type === 'streams' && is.string(item)) {
|
|
21
|
+
else if (type === 'tables-streams' && is.string(item)) {
|
|
16
22
|
let name = item
|
|
17
23
|
let src = join(cwd, dir, name)
|
|
18
24
|
let table = name
|
|
19
25
|
return { name, src, table }
|
|
20
26
|
}
|
|
21
|
-
else if (type === 'streams' && is.object(item)) {
|
|
27
|
+
else if (type === 'tables-streams' && is.object(item)) {
|
|
22
28
|
let name = Object.keys(item)[0]
|
|
23
29
|
let src = item[name].src
|
|
24
30
|
? join(cwd, item[name].src)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
let is = require('../../../lib/is')
|
|
1
2
|
let { aliases, runtimes, runtimeList } = require('lambda-runtimes')
|
|
2
3
|
|
|
3
4
|
// Runtime interpolater
|
|
@@ -18,5 +19,9 @@ module.exports = function getRuntime (config) {
|
|
|
18
19
|
config.runtimeAlias = runtime
|
|
19
20
|
}
|
|
20
21
|
}
|
|
22
|
+
else if (is.defined(runtime)) {
|
|
23
|
+
// Someone did something funky like specify a number or bool, so coerce and let it fail validation
|
|
24
|
+
config.runtime = `${config.runtime}`
|
|
25
|
+
}
|
|
21
26
|
return config
|
|
22
27
|
}
|
|
@@ -10,7 +10,7 @@ let getEvents = require('./_events')
|
|
|
10
10
|
let getPlugins = require('./_plugins')
|
|
11
11
|
let getScheduled = require('./_scheduled')
|
|
12
12
|
let getWS = require('./_websockets')
|
|
13
|
-
let
|
|
13
|
+
let getTablesStreams = require('./_tables-streams')
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Build out the Lambda tree
|
|
@@ -87,6 +87,8 @@ function populateLambda (type, pragma, inventory, errors) {
|
|
|
87
87
|
return lambdas
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
let ts = 'tables-streams'
|
|
91
|
+
|
|
90
92
|
function getLambda (params) {
|
|
91
93
|
let { type } = params
|
|
92
94
|
params.dir = `src/${type}/`
|
|
@@ -96,8 +98,8 @@ function getLambda (params) {
|
|
|
96
98
|
if (type === 'plugins') return getPlugins(params)
|
|
97
99
|
if (type === 'queues') return getEvents(params) // Effectively the same as events
|
|
98
100
|
if (type === 'scheduled') return getScheduled(params)
|
|
99
|
-
if (type ===
|
|
100
|
-
if (type === 'tables') return
|
|
101
|
+
if (type === ts) return getTablesStreams(params)
|
|
102
|
+
if (type === 'tables') return getTablesStreams(params) // Shortcut for creating streams
|
|
101
103
|
/* istanbul ignore else */ /* Clearer to be explicit here */
|
|
102
104
|
if (type === 'ws') return getWS(params)
|
|
103
105
|
}
|
|
@@ -108,7 +110,7 @@ module.exports = {
|
|
|
108
110
|
plugins: populateLambda.bind({}, 'plugins'),
|
|
109
111
|
queues: populateLambda.bind({}, 'queues'),
|
|
110
112
|
scheduled: populateLambda.bind({}, 'scheduled'),
|
|
111
|
-
streams: populateLambda.bind({}, 'streams'),
|
|
112
113
|
tables: populateLambda.bind({}, 'tables'),
|
|
114
|
+
[ts]: populateLambda.bind({}, ts),
|
|
113
115
|
ws: populateLambda.bind({}, 'ws'),
|
|
114
116
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
let is = require('../../lib/is')
|
|
2
|
+
let validate = require('./validate')
|
|
3
|
+
|
|
4
|
+
function configureTablesIndexes ({ arc, errors }) {
|
|
5
|
+
if (!arc['tables-indexes'] || !arc['tables-indexes'].length) return null
|
|
6
|
+
if (arc['tables-indexes'] && !arc.tables) {
|
|
7
|
+
errors.push(`Specifying @tables-indexes requires specifying corresponding @tables`)
|
|
8
|
+
return null
|
|
9
|
+
}
|
|
10
|
+
if (arc['tables-indexes']?.length && arc.indexes?.length) {
|
|
11
|
+
errors.push(`Either @tables-indexes or @indexes can be specified, but not both`)
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let indexes = getIndexes(arc, 'tables-indexes', errors)
|
|
16
|
+
validate.indexes(indexes, '@tables-indexes', errors)
|
|
17
|
+
|
|
18
|
+
return indexes
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let getIndexes = (arc, pragma, errors) => {
|
|
22
|
+
let isCustomName = key => is.string(key) && key.toLowerCase() === 'name'
|
|
23
|
+
function error (item) { errors.push(`Invalid @${pragma} item: ${item}`) }
|
|
24
|
+
return arc[pragma].map(index => {
|
|
25
|
+
if (is.object(index)) {
|
|
26
|
+
let name = Object.keys(index)[0]
|
|
27
|
+
let partitionKey = null
|
|
28
|
+
let partitionKeyType = null
|
|
29
|
+
let sortKey = null
|
|
30
|
+
let sortKeyType = null
|
|
31
|
+
let indexName = null
|
|
32
|
+
Object.entries(index[name]).forEach(([ key, value ]) => {
|
|
33
|
+
if (is.sortKey(value)) {
|
|
34
|
+
sortKey = key
|
|
35
|
+
sortKeyType = value.replace('**', '')
|
|
36
|
+
}
|
|
37
|
+
else if (is.primaryKey(value)) {
|
|
38
|
+
partitionKey = key
|
|
39
|
+
partitionKeyType = value.replace('*', '')
|
|
40
|
+
}
|
|
41
|
+
else if (isCustomName(key)) {
|
|
42
|
+
indexName = value
|
|
43
|
+
}
|
|
44
|
+
})
|
|
45
|
+
return {
|
|
46
|
+
indexName,
|
|
47
|
+
name,
|
|
48
|
+
partitionKey,
|
|
49
|
+
partitionKeyType,
|
|
50
|
+
sortKey,
|
|
51
|
+
sortKeyType,
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
error(index)
|
|
55
|
+
}).filter(Boolean) // Invalid indexes may create undefined entries in the map
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
configureTablesIndexes.getIndexes = getIndexes
|
|
59
|
+
module.exports = configureTablesIndexes
|
|
@@ -3,15 +3,15 @@ let validate = require('./validate')
|
|
|
3
3
|
let is = require('../../lib/is')
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* `@streams`
|
|
6
|
+
* `@tables-streams`
|
|
7
7
|
* - Originally `@tables {tablename} stream true` created a lambda at src/tables/{tablename}
|
|
8
|
-
* - This was superseded by `@streams`; `@tables` remains for backwards compat and as a convenience for creating `@streams`
|
|
8
|
+
* - This was superseded by `@tables-streams`; `@tables` remains for backwards compat and as a convenience for creating `@tables-streams`
|
|
9
9
|
* - If a project has an existing `@tables` Lambda, we'll continue using that so long as the directory exists
|
|
10
10
|
*/
|
|
11
|
-
module.exports = function
|
|
12
|
-
if (!arc
|
|
13
|
-
if (arc
|
|
14
|
-
errors.push(`Specifying @streams requires specifying corresponding @tables`)
|
|
11
|
+
module.exports = function configureTablesStreams ({ arc, inventory, errors }) {
|
|
12
|
+
if (!arc['tables-streams'] && !arc.tables) return null
|
|
13
|
+
if (arc['tables-streams'] && !arc.tables) {
|
|
14
|
+
errors.push(`Specifying @tables-streams requires specifying corresponding @tables`)
|
|
15
15
|
return null
|
|
16
16
|
}
|
|
17
17
|
|
|
@@ -22,21 +22,21 @@ module.exports = function configureStreams ({ arc, inventory, errors }) {
|
|
|
22
22
|
}
|
|
23
23
|
else tables = null
|
|
24
24
|
|
|
25
|
-
// Populate @streams
|
|
26
|
-
let streams = populate
|
|
25
|
+
// Populate @tables-streams
|
|
26
|
+
let streams = populate['tables-streams'](arc['tables-streams'], inventory, errors)
|
|
27
27
|
|
|
28
28
|
if (tables && streams) {
|
|
29
29
|
let uniqueTables = tables.filter(t => !streams.some(s => s.table === t.table))
|
|
30
30
|
let merged = streams.concat(uniqueTables)
|
|
31
|
-
validate.
|
|
31
|
+
validate.tablesStreams(merged, errors)
|
|
32
32
|
return merged
|
|
33
33
|
}
|
|
34
34
|
else if (streams) {
|
|
35
|
-
validate.
|
|
35
|
+
validate.tablesStreams(streams, errors)
|
|
36
36
|
return streams
|
|
37
37
|
}
|
|
38
38
|
else if (tables) {
|
|
39
|
-
validate.
|
|
39
|
+
validate.tablesStreams(tables, errors)
|
|
40
40
|
return tables
|
|
41
41
|
}
|
|
42
42
|
return null
|
|
@@ -53,7 +53,6 @@ function validateCron (schedule, errors) {
|
|
|
53
53
|
if (!year.toString().match(minHrYr)) expErr('year', year)
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
|
|
57
56
|
let singular = [ 'minute', 'hour', 'day' ]
|
|
58
57
|
let plural = [ 'minutes', 'hours', 'days' ]
|
|
59
58
|
function validateRate (schedule, errors) {
|
|
@@ -69,11 +68,7 @@ function validateRate (schedule, errors) {
|
|
|
69
68
|
|
|
70
69
|
// Value must be a >0 number
|
|
71
70
|
if (!is.number(value) || !(value > 0)) {
|
|
72
|
-
expErr('rate value must be a number greater than 0', value)
|
|
73
|
-
}
|
|
74
|
-
// Value must be a whole number
|
|
75
|
-
else if (!value.toString().match(/^\d+$/)) {
|
|
76
|
-
expErr('rate value must be a whole number', value)
|
|
71
|
+
expErr('rate value must be a whole number greater than 0', value)
|
|
77
72
|
}
|
|
78
73
|
// Interval must be a string
|
|
79
74
|
if (!is.string(interval)) {
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
let { regex, size, unique } = require('./_lib')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validate @tables-streams (& @tables streams true)
|
|
5
|
+
*
|
|
6
|
+
* Where possible, attempts to follow DynamoDB validation
|
|
7
|
+
* See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function validateTablesStreams (tablesStreams, errors) {
|
|
10
|
+
if (tablesStreams.length) {
|
|
11
|
+
unique(tablesStreams, '@tables-streams', errors)
|
|
12
|
+
|
|
13
|
+
tablesStreams.forEach(stream => {
|
|
14
|
+
let { name } = stream
|
|
15
|
+
size(name, 3, 255, '@tables-streams', errors)
|
|
16
|
+
regex(name, 'veryLooseName', '@tables-streams', errors)
|
|
17
|
+
})
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -47,7 +47,7 @@ module.exports = function validateTablesAndIndexes (pragma, pragmaName, errors)
|
|
|
47
47
|
}
|
|
48
48
|
})
|
|
49
49
|
if (foundDupe) {
|
|
50
|
-
let err = `Duplicate
|
|
50
|
+
let err = `Duplicate ${pragmaName} value: '${index.name}'`
|
|
51
51
|
if (!errors.includes(err)) errors.push(err)
|
|
52
52
|
}
|
|
53
53
|
})
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/* eslint-disable global-require */
|
|
2
2
|
module.exports = {
|
|
3
3
|
// Pragmas and project validation
|
|
4
|
-
aws:
|
|
5
|
-
events:
|
|
6
|
-
http:
|
|
7
|
-
indexes:
|
|
8
|
-
proxy:
|
|
9
|
-
tables:
|
|
10
|
-
queues:
|
|
11
|
-
scheduled:
|
|
12
|
-
shared:
|
|
13
|
-
|
|
14
|
-
websockets:
|
|
4
|
+
aws: require('./_aws'),
|
|
5
|
+
events: require('./_events'),
|
|
6
|
+
http: require('./_http'),
|
|
7
|
+
indexes: require('./_tables'), // Same ruleset as @tables (more or less)
|
|
8
|
+
proxy: require('./_proxy'),
|
|
9
|
+
tables: require('./_tables'),
|
|
10
|
+
queues: require('./_events'), // Same ruleset as @events
|
|
11
|
+
scheduled: require('./_scheduled'),
|
|
12
|
+
shared: require('./_shared'), // Also includes @views
|
|
13
|
+
tablesStreams: require('./_tables-streams'),
|
|
14
|
+
websockets: require('./_websockets'),
|
|
15
15
|
|
|
16
16
|
// Misc
|
|
17
|
-
validate:
|
|
17
|
+
validate: require('./_lib')
|
|
18
18
|
}
|
package/src/defaults/index.js
CHANGED
|
@@ -60,8 +60,9 @@ module.exports = function inventoryDefaults (params = {}) {
|
|
|
60
60
|
scheduled: null,
|
|
61
61
|
shared: null,
|
|
62
62
|
static: null,
|
|
63
|
-
streams: null,
|
|
64
63
|
tables: null,
|
|
64
|
+
'tables-indexes': null,
|
|
65
|
+
'tables-streams': null,
|
|
65
66
|
views: null,
|
|
66
67
|
ws: null,
|
|
67
68
|
// Collection of all Lambda paths
|
package/src/get.js
CHANGED
|
@@ -8,7 +8,7 @@ module.exports = function _get (inventory) {
|
|
|
8
8
|
if (pragma === null) return null
|
|
9
9
|
if (is.array(pragma)) {
|
|
10
10
|
// Handle arrays of named entities or string values
|
|
11
|
-
let finder = i => i
|
|
11
|
+
let finder = i => i?.name === name || i === name
|
|
12
12
|
if (multipleResults.includes(prag)) {
|
|
13
13
|
let results = pragma.filter(finder)
|
|
14
14
|
return results.length ? results : undefined
|
|
@@ -37,5 +37,6 @@ module.exports = function _get (inventory) {
|
|
|
37
37
|
// Everything is uniquely named except in certain special-case pragmas
|
|
38
38
|
// These refer to other pragmas, and thus may allow multiple same/same-named entities
|
|
39
39
|
let multipleResults = [
|
|
40
|
-
'indexes'
|
|
40
|
+
'tables-indexes',
|
|
41
|
+
'indexes',
|
|
41
42
|
]
|
package/src/lib/is.js
CHANGED
|
@@ -4,9 +4,10 @@ module.exports = {
|
|
|
4
4
|
// Types
|
|
5
5
|
array: item => Array.isArray(item),
|
|
6
6
|
bool: item => typeof item === 'boolean',
|
|
7
|
-
number: item =>
|
|
7
|
+
number: item => Number.isInteger(item),
|
|
8
8
|
object: item => typeof item === 'object' && !Array.isArray(item),
|
|
9
9
|
string: item => typeof item === 'string',
|
|
10
|
+
defined: item => typeof item !== 'undefined' && item !== null,
|
|
10
11
|
// Filesystem
|
|
11
12
|
exists: path => existsSync(path),
|
|
12
13
|
folder: path => existsSync(path) && lstatSync(path).isDirectory(),
|
package/src/lib/pragmas.js
CHANGED
|
@@ -6,7 +6,7 @@ module.exports = {
|
|
|
6
6
|
'cdn',
|
|
7
7
|
'events',
|
|
8
8
|
'http',
|
|
9
|
-
'indexes',
|
|
9
|
+
'indexes', // -> transitioning to @tables-indexes
|
|
10
10
|
'macros',
|
|
11
11
|
'plugins',
|
|
12
12
|
'proxy',
|
|
@@ -14,8 +14,9 @@ module.exports = {
|
|
|
14
14
|
'scheduled',
|
|
15
15
|
'shared',
|
|
16
16
|
'static',
|
|
17
|
-
'streams',
|
|
18
17
|
'tables',
|
|
18
|
+
'tables-indexes',
|
|
19
|
+
'tables-streams',
|
|
19
20
|
'views',
|
|
20
21
|
'ws',
|
|
21
22
|
],
|
|
@@ -26,7 +27,7 @@ module.exports = {
|
|
|
26
27
|
'plugins',
|
|
27
28
|
'queues',
|
|
28
29
|
'scheduled',
|
|
29
|
-
'streams',
|
|
30
|
+
'tables-streams',
|
|
30
31
|
'ws',
|
|
31
32
|
],
|
|
32
33
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
let is = require('../lib/is')
|
|
2
|
+
let { lambdas } = require('../lib/pragmas')
|
|
3
|
+
let { aliases, runtimeList } = require('lambda-runtimes')
|
|
4
|
+
let allRuntimes = runtimeList.concat([ 'deno' ])
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Configuration validator
|
|
8
|
+
*/
|
|
9
|
+
module.exports = function configValidator (params, inventory, errors) {
|
|
10
|
+
let { runtime: globalRuntime, memory: globalMemory, timeout: globalTimeout } = inventory.aws
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Global config
|
|
14
|
+
*/
|
|
15
|
+
// Memory
|
|
16
|
+
if (is.defined(globalMemory) && invalidMemory(globalMemory)) {
|
|
17
|
+
errors.push(invalidMemoryMsg(`${globalMemory} MB (@aws)`))
|
|
18
|
+
}
|
|
19
|
+
// Runtime
|
|
20
|
+
if ((globalRuntime && !is.string(globalRuntime)) ||
|
|
21
|
+
(globalRuntime && !allRuntimes.includes(globalRuntime) &&
|
|
22
|
+
!aliases[globalRuntime] && !aliases[globalRuntime.toLowerCase()])) {
|
|
23
|
+
errors.push(`Invalid project-level runtime: ${globalRuntime}`)
|
|
24
|
+
}
|
|
25
|
+
// Timeout
|
|
26
|
+
if (is.defined(globalTimeout) && invalidTimeout(globalTimeout)) {
|
|
27
|
+
errors.push(invalidTimeoutMsg(`${globalTimeout} seconds (@aws)`))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Lambda config
|
|
32
|
+
*/
|
|
33
|
+
lambdas.forEach(p => {
|
|
34
|
+
let pragma = inventory[p]
|
|
35
|
+
if (pragma) pragma.forEach(({ name, config }) => {
|
|
36
|
+
let { memory, runtime, timeout } = config
|
|
37
|
+
|
|
38
|
+
// Memory
|
|
39
|
+
if (invalidMemory(memory) && memory !== globalMemory) {
|
|
40
|
+
errors.push(invalidMemoryMsg(`${memory} MB (@${p} ${name})`))
|
|
41
|
+
}
|
|
42
|
+
// Runtime
|
|
43
|
+
if (!allRuntimes.includes(runtime) && runtime !== globalRuntime) {
|
|
44
|
+
errors.push(`Invalid runtime: ${runtime} (@${p} ${name})`)
|
|
45
|
+
}
|
|
46
|
+
// Timeout
|
|
47
|
+
if (invalidTimeout(timeout) && timeout !== globalTimeout) {
|
|
48
|
+
errors.push(invalidTimeoutMsg(`${timeout} seconds (@${p} ${name})`))
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Memory
|
|
55
|
+
let minMemory = 128
|
|
56
|
+
let maxMemory = 10240
|
|
57
|
+
let invalidMemory = memory => !is.number(memory) || (memory < minMemory) || (memory > maxMemory)
|
|
58
|
+
let invalidMemoryMsg = info => `Invalid Lambda memory setting: ${info}, memory must be between ${minMemory} - ${maxMemory} MB`
|
|
59
|
+
|
|
60
|
+
// Timeout
|
|
61
|
+
let minTimeout = 1
|
|
62
|
+
let maxTimeout = 1 * 60 * 15 // 15 mins
|
|
63
|
+
let invalidTimeout = timeout => !is.number(timeout) || (timeout < minTimeout) || (timeout > maxTimeout)
|
|
64
|
+
let invalidTimeoutMsg = info => `Invalid Lambda timeout setting: ${info}, timeout must be between ${minTimeout} - ${maxTimeout} seconds`
|
package/src/validate/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
let
|
|
1
|
+
let config = require('./config')
|
|
2
2
|
let layers = require('./layers')
|
|
3
3
|
let tablesChildren = require('./tables-children')
|
|
4
4
|
let errorFmt = require('../lib/error-fmt')
|
|
@@ -12,9 +12,8 @@ module.exports = function finalValidation (params, inventory) {
|
|
|
12
12
|
/**
|
|
13
13
|
* Deal with vendor configuration errors
|
|
14
14
|
*/
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
runtimes(params, inventory, errors)
|
|
15
|
+
// Analyze function configuration
|
|
16
|
+
config(params, inventory, errors)
|
|
18
17
|
|
|
19
18
|
// Ensure layer configuration will work, AWS blows up with awful errors on this
|
|
20
19
|
layers(params, inventory, errors)
|
|
@@ -32,8 +31,7 @@ module.exports = function finalValidation (params, inventory) {
|
|
|
32
31
|
/**
|
|
33
32
|
* Deal with project validation errors
|
|
34
33
|
*/
|
|
35
|
-
|
|
36
|
-
// Ensure @tables children (@streams, @indexes) have parent tables present
|
|
34
|
+
// Ensure @tables children (@tables-streams, @indexes) have parent tables present
|
|
37
35
|
tablesChildren(inventory, errors)
|
|
38
36
|
|
|
39
37
|
if (errors.length) {
|
package/src/validate/layers.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
let { sep } = require('path')
|
|
2
2
|
let { lambdas } = require('../lib/pragmas')
|
|
3
|
-
let
|
|
3
|
+
let is = require('../lib/is')
|
|
4
|
+
let plural = arr => arr.length > 1 ? 's' : ''
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Layer validator
|
|
7
8
|
*/
|
|
8
9
|
module.exports = function layerValidator (params, inventory, errors) {
|
|
9
|
-
let {
|
|
10
|
+
let { _project } = inventory
|
|
11
|
+
let { region, layers: globalLayers } = inventory.aws
|
|
10
12
|
let { cwd, validateLayers = true } = params
|
|
11
13
|
|
|
12
14
|
// Shouldn't be possible because we backfill region, but jic
|
|
@@ -15,39 +17,69 @@ module.exports = function layerValidator (params, inventory, errors) {
|
|
|
15
17
|
// Allow for manual opt-out of layer validation
|
|
16
18
|
if (!validateLayers) return
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
})
|
|
34
|
-
}
|
|
20
|
+
/**
|
|
21
|
+
* Global config
|
|
22
|
+
*/
|
|
23
|
+
let location = _project?.manifest?.replace(cwd, '')
|
|
24
|
+
validateLayer({ layers: globalLayers, region, location, errors })
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lambda config
|
|
28
|
+
*/
|
|
29
|
+
lambdas.forEach(p => {
|
|
30
|
+
let pragma = inventory[p]
|
|
31
|
+
if (pragma) pragma.forEach(({ config, configFile }) => {
|
|
32
|
+
let location = configFile?.replace(cwd, '')
|
|
33
|
+
validateLayer({ layers: config.layers, region, location, errors })
|
|
34
|
+
})
|
|
35
35
|
})
|
|
36
|
+
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
// CloudFormation fails without a helpful error if any layers aren't in the same region as the app because CloudFormation
|
|
47
|
-
for (let arn of layers) {
|
|
48
|
-
let arnError = validateARN({ arn, region, loc })
|
|
49
|
-
if (arnError) errors.push(arnError)
|
|
50
|
-
}
|
|
38
|
+
function validateLayer ({ layers, region, location, errors }) {
|
|
39
|
+
let loc = location && location.startsWith(sep) ? location.substr(1) : location
|
|
40
|
+
let config = loc ? ` (${loc})` : ''
|
|
41
|
+
if (!layers || !layers.length) return
|
|
42
|
+
else {
|
|
43
|
+
if (layers.length > 5) {
|
|
44
|
+
let list = `${layers.map(l => ` - ${l}`).join('\n')}`
|
|
45
|
+
errors.push(`Lambdas can only be configured with up to 5 layers, got ${layers.length} layers${config}:\n${list}`)
|
|
51
46
|
}
|
|
47
|
+
// CloudFormation fails without a helpful error if any layers aren't in the same region as the app because CloudFormation
|
|
48
|
+
let arnErrors = validateARN({ layers, region, config })
|
|
49
|
+
if (arnErrors) errors.push(arnErrors)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validates Lambda layer / policy ARNs, prob can't be used for other kinds of ARN
|
|
54
|
+
function validateARN ({ layers, region, config }) {
|
|
55
|
+
let invalidArns = []
|
|
56
|
+
let badRegions = []
|
|
57
|
+
layers.forEach(arn => {
|
|
58
|
+
let parts = is.string(arn) && arn.split(':')
|
|
59
|
+
// Invalid
|
|
60
|
+
if (!is.string(arn) ||
|
|
61
|
+
!arn.startsWith('arn:') ||
|
|
62
|
+
parts.length !== 8) {
|
|
63
|
+
return invalidArns.push(` - ${arn}`)
|
|
64
|
+
}
|
|
65
|
+
// Bad region
|
|
66
|
+
let layerRegion = parts[3]
|
|
67
|
+
if (region !== layerRegion) {
|
|
68
|
+
badRegions.push(
|
|
69
|
+
` - Layer ARN: ${arn}\n` +
|
|
70
|
+
` - Layer region: ${layerRegion}`
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
})
|
|
74
|
+
let err = ''
|
|
75
|
+
if (invalidArns.length) {
|
|
76
|
+
err += `Invalid ARN${plural(invalidArns)}${config}:\n` +
|
|
77
|
+
invalidArns.join('\n')
|
|
78
|
+
}
|
|
79
|
+
if (badRegions.length) {
|
|
80
|
+
err += `Layer${plural(badRegions)} ` +
|
|
81
|
+
`not in app's region of ${region}${config}:\n` +
|
|
82
|
+
badRegions.join('\n')
|
|
52
83
|
}
|
|
84
|
+
if (err) return err
|
|
53
85
|
}
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Ensure @tables children (@streams, @indexes) have parent tables present
|
|
2
|
+
* Ensure @tables children (@tables-streams, @indexes) have parent tables present
|
|
3
3
|
* - If not, configuration is invalid
|
|
4
4
|
*/
|
|
5
5
|
module.exports = function validateTablesChildren (inventory, errors) {
|
|
6
|
-
let { indexes, streams, tables } = inventory
|
|
6
|
+
let { indexes, 'tables-streams': tablesStreams, tables } = inventory
|
|
7
7
|
|
|
8
8
|
function check (table, type) {
|
|
9
9
|
if (!tables.some(t => t.name === table)) {
|
|
10
10
|
errors.push(`@${type} ${table} missing corresponding table`)
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
|
-
if (
|
|
14
|
-
|
|
13
|
+
if (tablesStreams) {
|
|
14
|
+
tablesStreams.forEach(stream => check(stream.table, 'tables-streams'))
|
|
15
15
|
}
|
|
16
16
|
if (indexes) {
|
|
17
17
|
indexes.forEach(index => check(index.name, 'indexes'))
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
let { regex, size, unique } = require('./_lib')
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Validate @streams (& @tables streams true)
|
|
5
|
-
*
|
|
6
|
-
* Where possible, attempts to follow DynamoDB validation
|
|
7
|
-
* See: https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html
|
|
8
|
-
*/
|
|
9
|
-
module.exports = function validateStreams (streams, errors) {
|
|
10
|
-
if (streams.length) {
|
|
11
|
-
unique(streams, '@streams', errors)
|
|
12
|
-
|
|
13
|
-
streams.forEach(stream => {
|
|
14
|
-
let { name } = stream
|
|
15
|
-
size(name, 3, 255, '@streams', errors)
|
|
16
|
-
regex(name, 'veryLooseName', '@streams', errors)
|
|
17
|
-
})
|
|
18
|
-
}
|
|
19
|
-
}
|
package/src/validate/arn.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
let is = require('../lib/is')
|
|
2
|
-
|
|
3
|
-
// Validates Lambda layer / policy ARNs, prob can't be used for other kinds of ARN
|
|
4
|
-
module.exports = function validateARN ({ arn, region, loc }) {
|
|
5
|
-
if (!is.string(arn) ||
|
|
6
|
-
!arn.startsWith('arn:') ||
|
|
7
|
-
arn.split(':').length !== 8) {
|
|
8
|
-
/* istanbul ignore next */
|
|
9
|
-
let lambda = loc ? `in ${loc}` : ''
|
|
10
|
-
return `Invalid ARN${lambda}: ${arn}`
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
let parts = arn.split(':')
|
|
14
|
-
let layerRegion = parts[3]
|
|
15
|
-
if (region !== layerRegion) {
|
|
16
|
-
/* istanbul ignore next */
|
|
17
|
-
let lambda = loc ? ` - Lambda: ${loc}\n` : ''
|
|
18
|
-
let err = `Lambda layers must be in the same region as app\n` + lambda +
|
|
19
|
-
` - App region: ${region}\n` +
|
|
20
|
-
` - Layer ARN: ${arn}\n` +
|
|
21
|
-
` - Layer region: ${layerRegion}`
|
|
22
|
-
return err
|
|
23
|
-
}
|
|
24
|
-
}
|
package/src/validate/runtimes.js
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
let { lambdas } = require('../lib/pragmas')
|
|
2
|
-
let { aliases, runtimeList } = require('lambda-runtimes')
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Runtime validator
|
|
6
|
-
*/
|
|
7
|
-
module.exports = function runtimeValidator (params, inventory, errors) {
|
|
8
|
-
|
|
9
|
-
let allRuntimes = runtimeList.concat([ 'deno' ])
|
|
10
|
-
let globalRuntime = inventory.aws?.runtime
|
|
11
|
-
if (globalRuntime &&
|
|
12
|
-
!allRuntimes.includes(globalRuntime) &&
|
|
13
|
-
!aliases[globalRuntime]) {
|
|
14
|
-
errors.push(`Invalid project-level runtime: ${globalRuntime}`)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
// Walk the tree of layer configs, starting with @aws
|
|
18
|
-
lambdas.forEach(p => {
|
|
19
|
-
let pragma = inventory[p]
|
|
20
|
-
if (pragma) pragma.forEach(entry => {
|
|
21
|
-
let runtime = entry.config.runtime
|
|
22
|
-
if (runtime === globalRuntime) return
|
|
23
|
-
if (!allRuntimes.includes(runtime)) {
|
|
24
|
-
errors.push(`Invalid runtime: ${runtime} (@${p} ${entry.name})`)
|
|
25
|
-
}
|
|
26
|
-
})
|
|
27
|
-
})
|
|
28
|
-
}
|