@architect/inventory 3.0.0-RC.5 → 3.0.0-RC.9
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 +8 -0
- package/package.json +7 -7
- package/src/config/pragmas/plugins.js +8 -3
- package/src/config/pragmas/populate-lambda/index.js +39 -7
- package/src/config/pragmas/static.js +4 -2
- package/src/config/pragmas/validate/_lib.js +3 -10
- package/src/config/pragmas/views.js +2 -2
- package/src/config/project/index.js +1 -1
- package/src/config/project/plugins/env.js +50 -12
- package/src/config/project/plugins/runtimes.js +44 -33
- package/src/config/project/prefs/dotenv.js +24 -55
- package/src/config/project/prefs/index.js +5 -4
- package/src/defaults/index.js +3 -3
- package/src/lib/get-lambda-dirs.js +3 -3
- package/src/lib/index.js +10 -0
- package/src/validate/index.js +16 -9
- package/src/validate/paths.js +17 -0
package/changelog.md
CHANGED
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
- Breaking change: renamed `lambda.handlerFunction` to `lambda.handlerMethod`
|
|
38
38
|
- Breaking change: prioritize `mod.ts|js` handlers in Deno Lambdas
|
|
39
39
|
- Breaking change: removed `toml` support
|
|
40
|
+
- Breaking change: removed `get.macros` method; as `@macros` are now automatically mapped to the Architect plugins, you can simply use `get.plugins` instead
|
|
40
41
|
- Performance improvements to building `inv.shared` + `inv.views`
|
|
41
42
|
- Improved memory footprint of Inventory object by preserving references in `lambdaSrcDirs`, `lambdasBySrcDir`
|
|
42
43
|
- Added `pragma` property to all Lambdas to aid in reference preservation
|
|
@@ -44,6 +45,13 @@
|
|
|
44
45
|
Update CI
|
|
45
46
|
- Stop publishing to the GitHub Package registry
|
|
46
47
|
|
|
48
|
+
|
|
49
|
+
### Fixed
|
|
50
|
+
|
|
51
|
+
- Added file path validation because `aws-sdk` blows up on !ascii paths; fixes #1292, thanks @GustMartins!
|
|
52
|
+
- Fixed env var validation from preference files
|
|
53
|
+
- Fixed error bubbling when reading a preferences file with issues
|
|
54
|
+
|
|
47
55
|
---
|
|
48
56
|
|
|
49
57
|
## [2.2.1] 2021-11-22
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@architect/inventory",
|
|
3
|
-
"version": "3.0.0-RC.
|
|
3
|
+
"version": "3.0.0-RC.9",
|
|
4
4
|
"description": "Architect project resource enumeration utility",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@architect/asap": "~5.0.0-RC.0",
|
|
25
25
|
"@architect/parser": "~6.0.0-RC.0",
|
|
26
|
-
"@architect/utils": "~3.0.
|
|
26
|
+
"@architect/utils": "~3.1.0-RC.0",
|
|
27
27
|
"lambda-runtimes": "~1.1.1"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"@architect/eslint-config": "~2.0.1",
|
|
31
31
|
"aws-sdk": "2.1001.0",
|
|
32
|
-
"aws-sdk-mock": "~5.6.
|
|
32
|
+
"aws-sdk-mock": "~5.6.2",
|
|
33
33
|
"cross-env": "~7.0.3",
|
|
34
|
-
"dotenv": "~
|
|
35
|
-
"eslint": "~8.
|
|
34
|
+
"dotenv": "~15.0.0",
|
|
35
|
+
"eslint": "~8.8.0",
|
|
36
36
|
"mock-fs": "~5.1.2",
|
|
37
37
|
"mock-require": "~3.0.3",
|
|
38
38
|
"nyc": "~15.1.0",
|
|
39
39
|
"tap-spec": "^5.0.0",
|
|
40
|
-
"tape": "^5.
|
|
40
|
+
"tape": "^5.5.0"
|
|
41
41
|
},
|
|
42
42
|
"eslintConfig": {
|
|
43
43
|
"extends": "@architect/eslint-config"
|
|
44
44
|
},
|
|
45
|
-
"
|
|
45
|
+
"nyc": {
|
|
46
46
|
"check-coverage": true,
|
|
47
47
|
"branches": 100,
|
|
48
48
|
"lines": 100,
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
let { join } = require('path')
|
|
2
2
|
let { existsSync } = require('fs')
|
|
3
|
-
let { is, normalizeSrc, pragmas } = require('../../lib')
|
|
3
|
+
let { is, normalizeSrc, pragmas, validationPatterns } = require('../../lib')
|
|
4
4
|
let { lambdas } = pragmas
|
|
5
5
|
let nonLambdaSetters = [ 'customLambdas', 'env', 'runtimes' ]
|
|
6
6
|
let setters = [ ...lambdas, ...nonLambdaSetters ]
|
|
7
7
|
let pluginMethods = [ 'deploy', 'sandbox' ] // TODO add more!
|
|
8
|
+
let reservedNames = [ '_methods', 'events', 'queues', 'static', 'tables' ]
|
|
8
9
|
|
|
9
10
|
module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
10
11
|
if (!arc?.plugins?.length && !arc?.macros?.length) return null
|
|
@@ -33,8 +34,12 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
|
33
34
|
: join(cwd, 'src', type + 's', name)
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
if (name
|
|
37
|
-
errors.push(
|
|
37
|
+
if (reservedNames.includes(name)) {
|
|
38
|
+
errors.push(`Plugin name ${name} is reserved, please rename your plugin`)
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
if (!validationPatterns.veryLooseName.test(name)) {
|
|
42
|
+
errors.push('Plugin names can only contain [a-zA-Z0-9/\\-._]')
|
|
38
43
|
continue
|
|
39
44
|
}
|
|
40
45
|
if (pluginPath) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
let { sep } = require('path')
|
|
2
|
+
let { deepFrozenCopy } = require('@architect/utils')
|
|
1
3
|
let read = require('../../../read')
|
|
2
4
|
let getLambda = require('./get-lambda')
|
|
3
5
|
let getRuntime = require('./get-runtime')
|
|
@@ -9,22 +11,45 @@ let { compiledRuntimes, is } = require('../../../lib')
|
|
|
9
11
|
/**
|
|
10
12
|
* Build out the Lambda tree from the Arc manifest or a passed pragma, and plugins
|
|
11
13
|
*/
|
|
12
|
-
function populateLambda (type,
|
|
14
|
+
function populateLambda (type, params) {
|
|
15
|
+
// Passing a pragma array via params allows special overrides
|
|
16
|
+
// See: @tables populating inv['tables-streams']
|
|
17
|
+
let { arc, inventory, errors, pragma } = params
|
|
18
|
+
|
|
13
19
|
let plugins = inventory.plugins?._methods?.set?.[type]
|
|
14
20
|
let pluginLambda = []
|
|
15
21
|
if (plugins) {
|
|
22
|
+
let invCopy = deepFrozenCopy(inventory)
|
|
16
23
|
let pluginResults = plugins.flatMap(fn => {
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
try {
|
|
25
|
+
var result = fn({ arc: invCopy._project.arc, inventory: { inv: invCopy } })
|
|
26
|
+
}
|
|
27
|
+
catch (err) {
|
|
28
|
+
err.message = `Setter plugin exception: plugin: ${fn.plugin}, method: set.${type}`
|
|
29
|
+
+ `\n` + err.message
|
|
30
|
+
throw err
|
|
31
|
+
}
|
|
32
|
+
if (!result ||
|
|
33
|
+
(!is.object(result) && !is.array(result)) ||
|
|
34
|
+
(is.array(result) && result.some(r => !is.object(r)))) {
|
|
19
35
|
errors.push(`Setter plugins must return a valid response: plugin: ${fn.plugin}, method: set.${type}`)
|
|
20
36
|
return []
|
|
21
37
|
}
|
|
22
|
-
result
|
|
23
|
-
|
|
38
|
+
if (is.array(result)) {
|
|
39
|
+
result.forEach(item => {
|
|
40
|
+
item.plugin = fn.plugin
|
|
41
|
+
item.type = fn.type
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
result.plugin = fn.plugin
|
|
46
|
+
result.type = fn.type
|
|
47
|
+
}
|
|
24
48
|
return result
|
|
25
49
|
})
|
|
26
50
|
pluginLambda = populate(type, pluginResults, inventory, errors, true) || []
|
|
27
51
|
}
|
|
52
|
+
|
|
28
53
|
let pragmaLambda = populate(type, pragma || arc[type], inventory, errors) || []
|
|
29
54
|
let aggregate = [ ...pluginLambda, ...pragmaLambda ]
|
|
30
55
|
return aggregate.length ? aggregate : null
|
|
@@ -46,6 +71,11 @@ function populate (type, pragma, inventory, errors, plugin) {
|
|
|
46
71
|
if (!result) continue
|
|
47
72
|
|
|
48
73
|
let { name, src, build } = result
|
|
74
|
+
|
|
75
|
+
// Normalize paths, especially since plugin authors may not use path.join
|
|
76
|
+
src = normalize(src)
|
|
77
|
+
if (build) build = normalize(build)
|
|
78
|
+
|
|
49
79
|
// Set up fresh config, then overlay plugin config
|
|
50
80
|
let config = defaultProjectConfig()
|
|
51
81
|
config = { ...config, ...getKnownProps(configProps, result.config) }
|
|
@@ -82,7 +112,7 @@ function populate (type, pragma, inventory, errors, plugin) {
|
|
|
82
112
|
|
|
83
113
|
// Tidy up any irrelevant properties
|
|
84
114
|
if (!compiledRuntimes.includes(config.runtimeConfig?.type)) {
|
|
85
|
-
//
|
|
115
|
+
// Super important! If we don't clean up the build prop, many explosions will explode
|
|
86
116
|
build = undefined
|
|
87
117
|
}
|
|
88
118
|
if (type !== 'http') {
|
|
@@ -111,8 +141,10 @@ function populate (type, pragma, inventory, errors, plugin) {
|
|
|
111
141
|
return lambdas
|
|
112
142
|
}
|
|
113
143
|
|
|
144
|
+
let normalize = path => path.replace(/[\\\/]/g, sep)
|
|
145
|
+
|
|
114
146
|
// Lambda setter plugins can technically return anything, so this ensures everything is tidy
|
|
115
|
-
let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table' ]
|
|
147
|
+
let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table', 'type' ]
|
|
116
148
|
let configProps = [ ...Object.keys(defaultFunctionConfig()), 'fifo', 'views' ]
|
|
117
149
|
let getKnownProps = (knownProps, raw = {}) => {
|
|
118
150
|
let props = knownProps.flatMap(prop => is.defined(raw[prop]) ? [ [ prop, raw[prop] ] ] : [])
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
let { asapSrc, is } = require('../../lib')
|
|
2
2
|
|
|
3
3
|
module.exports = function configureStatic ({ arc, inventory }) {
|
|
4
|
+
let httpSetters = inventory.plugins?._methods?.set?.http
|
|
5
|
+
|
|
4
6
|
// @static is inferred by @http
|
|
5
|
-
if (!arc.static && !arc.http) return null
|
|
7
|
+
if (!arc.static && !arc.http && !httpSetters) return null
|
|
6
8
|
|
|
7
9
|
let staticPragma = arc.static || []
|
|
8
10
|
let _static = {
|
|
@@ -37,7 +39,7 @@ module.exports = function configureStatic ({ arc, inventory }) {
|
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
// Handy shortcut to ASAP for bare @static
|
|
40
|
-
if (!arc.http) {
|
|
42
|
+
if (!arc.http && !httpSetters) {
|
|
41
43
|
inventory._project.rootHandler = 'arcStaticAssetProxy'
|
|
42
44
|
inventory._project.asapSrc = asapSrc()
|
|
43
45
|
}
|
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
let { is } = require('../../../lib')
|
|
2
|
-
|
|
3
|
-
let patterns = {
|
|
4
|
-
looseName: new RegExp(/^[a-z][a-zA-Z0-9-_]+$/),
|
|
5
|
-
strictName: new RegExp(/^[a-z][a-z0-9-]+$/),
|
|
6
|
-
// DynamoDB, SNS, SQS
|
|
7
|
-
veryLooseName: new RegExp(/^[a-zA-Z0-9/\-._]*$/),
|
|
8
|
-
}
|
|
1
|
+
let { is, validationPatterns } = require('../../../lib')
|
|
9
2
|
|
|
10
3
|
function regex (value, pattern, pragmaName, errors) {
|
|
11
|
-
if (!
|
|
12
|
-
if (!
|
|
4
|
+
if (!validationPatterns[pattern]) throw ReferenceError(`Invalid validation pattern specified: ${pattern}`)
|
|
5
|
+
if (!validationPatterns[pattern].test(value)) errors.push(`Invalid ${pragmaName} item: '${value}' must match ${validationPatterns[pattern]}`)
|
|
13
6
|
}
|
|
14
7
|
|
|
15
8
|
function size (value, min, max, pragmaName, errors) {
|
|
@@ -48,8 +48,8 @@ module.exports = function configureViews ({ arc, pragmas, inventory, errors }) {
|
|
|
48
48
|
|
|
49
49
|
// Set new views settings
|
|
50
50
|
for (let view of arc.views) {
|
|
51
|
-
let method = view[0]
|
|
52
|
-
let path = view[1]
|
|
51
|
+
let method = view?.[0]?.toLowerCase()
|
|
52
|
+
let path = view?.[1]
|
|
53
53
|
if (method === 'src') continue
|
|
54
54
|
let name = `${method} ${path}`
|
|
55
55
|
let route = pragmas.http.find(n => n.name === name)
|
|
@@ -68,7 +68,7 @@ module.exports = function getProjectConfig (params) {
|
|
|
68
68
|
})
|
|
69
69
|
|
|
70
70
|
let { build, runtimes } = plugins.runtimes(params, _project)
|
|
71
|
-
if (build)
|
|
71
|
+
if (build) _project.build = join(_project.cwd, build)
|
|
72
72
|
if (runtimes) _project.customRuntimes = runtimes
|
|
73
73
|
}
|
|
74
74
|
|
|
@@ -1,33 +1,71 @@
|
|
|
1
|
-
let {
|
|
1
|
+
let { deepFrozenCopy } = require('@architect/utils')
|
|
2
|
+
let { is, validationPatterns: valid } = require('../../../lib')
|
|
3
|
+
let envs = [ 'testing', 'staging', 'production' ]
|
|
4
|
+
let str = value => {
|
|
5
|
+
if (is.object(value) || is.array(value)) return JSON.stringify(value)
|
|
6
|
+
return String(value)
|
|
7
|
+
}
|
|
2
8
|
|
|
3
9
|
module.exports = function setEnvPlugins (params, project) {
|
|
4
10
|
let { errors, inventory } = params
|
|
5
11
|
let envPlugins = inventory.plugins?._methods?.set?.env
|
|
6
12
|
if (envPlugins?.length) {
|
|
7
|
-
let env = {
|
|
13
|
+
let env = {
|
|
14
|
+
testing: null,
|
|
15
|
+
staging: null,
|
|
16
|
+
production: null,
|
|
17
|
+
}
|
|
8
18
|
|
|
9
19
|
// inventory._project is not yet built, so provide as much as we can to plugins for now
|
|
10
|
-
let inv = { ...inventory, _project: project }
|
|
20
|
+
let inv = deepFrozenCopy({ ...inventory, _project: project })
|
|
11
21
|
envPlugins.forEach(fn => {
|
|
12
22
|
let errType = `plugin: ${fn.plugin}, method: set.env`
|
|
13
23
|
try {
|
|
14
|
-
let result = fn({ inventory: { inv } })
|
|
24
|
+
let result = fn({ arc: inv._project.arc, inventory: { inv } })
|
|
15
25
|
if (!is.object(result) || !Object.keys(result).length) {
|
|
16
26
|
return errors.push(`Env plugin returned invalid data, must return an Object with one or more keys + values: ${errType}`)
|
|
17
27
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
28
|
+
// Populate env vars based on environment
|
|
29
|
+
// If any keys are environment names, disregard all keys except environment names
|
|
30
|
+
if (Object.keys(result).some(k => envs.includes(k))) {
|
|
31
|
+
envs.forEach(e => {
|
|
32
|
+
if (result[e]) Object.entries(result[e]).forEach(([ k, v ]) => {
|
|
33
|
+
if (!valid.envVar.test(k)) {
|
|
34
|
+
return errors.push(`Env var '${k}' is invalid, must be [a-zA-Z0-9_]`)
|
|
35
|
+
}
|
|
36
|
+
let errored = false, val = str(v)
|
|
37
|
+
if (!env[e]) env[e] = { [k]: val }
|
|
38
|
+
else if (env[e][k] && !errored) {
|
|
39
|
+
errored = true
|
|
40
|
+
errors.push(`Env var '${k}' already registered: ${errType}`)
|
|
41
|
+
}
|
|
42
|
+
else env[e][k] = val
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
}
|
|
46
|
+
// Populate all environments based on env var
|
|
47
|
+
else {
|
|
48
|
+
Object.entries(result).forEach(([ k, v ]) => {
|
|
49
|
+
if (!valid.envVar.test(k)) {
|
|
50
|
+
return errors.push(`Env var '${k}' is invalid, must be [a-zA-Z0-9_]`)
|
|
51
|
+
}
|
|
52
|
+
let errored = false, val = str(v)
|
|
53
|
+
envs.forEach(e => {
|
|
54
|
+
if (!env[e]) env[e] = { [k]: val }
|
|
55
|
+
else if (env[e][k] && !errored) {
|
|
56
|
+
errored = true
|
|
57
|
+
errors.push(`Env var '${k}' already registered: ${errType}`)
|
|
58
|
+
}
|
|
59
|
+
else env[e][k] = val
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
}
|
|
25
63
|
}
|
|
26
64
|
catch (err) {
|
|
27
65
|
errors.push(`Runtime plugin '${fn.plugin}' failed: ${err.message}`)
|
|
28
66
|
}
|
|
29
67
|
})
|
|
30
|
-
return
|
|
68
|
+
return env
|
|
31
69
|
}
|
|
32
70
|
return inventory._project.env.plugins
|
|
33
71
|
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
-
let { is } = require('../../../lib')
|
|
2
1
|
let { aliases, runtimeList } = require('lambda-runtimes')
|
|
2
|
+
let { deepFrozenCopy } = require('@architect/utils')
|
|
3
|
+
let { is, validationPatterns } = require('../../../lib')
|
|
4
|
+
let { looserName } = validationPatterns
|
|
3
5
|
let allRuntimes = runtimeList.concat([ 'deno', ...Object.keys(aliases) ])
|
|
6
|
+
let validTypes = [ 'transpiled', 'compiled', 'interpreted' ]
|
|
7
|
+
let builtTypes = validTypes.filter(t => t !== 'interpreted')
|
|
4
8
|
|
|
5
9
|
module.exports = function setRuntimePlugins (params, project) {
|
|
6
10
|
let { errors, inventory } = params
|
|
@@ -10,45 +14,52 @@ module.exports = function setRuntimePlugins (params, project) {
|
|
|
10
14
|
runtimes: [],
|
|
11
15
|
}
|
|
12
16
|
// inventory._project is not yet built, so provide as much as we can to plugins for now
|
|
13
|
-
let inv = { ...inventory, _project: project }
|
|
17
|
+
let inv = deepFrozenCopy({ ...inventory, _project: project })
|
|
14
18
|
let build
|
|
15
19
|
runtimePlugins.forEach(fn => {
|
|
16
20
|
let errType = `plugin: ${fn.plugin}, method: set.runtimes`
|
|
17
21
|
try {
|
|
18
|
-
|
|
19
|
-
result = is.array(result) ? result : [ result ]
|
|
20
|
-
result.forEach(runtime => {
|
|
21
|
-
// TODO add more validation
|
|
22
|
-
let { name } = runtime
|
|
23
|
-
if (!name) {
|
|
24
|
-
let msg = `Runtime plugin must provide a name: ${errType}`
|
|
25
|
-
return errors.push(msg)
|
|
26
|
-
}
|
|
27
|
-
if (allRuntimes.includes(name)) {
|
|
28
|
-
let msg = `Runtime name '${name}' is reserved: ${errType}`
|
|
29
|
-
return errors.push(msg)
|
|
30
|
-
}
|
|
31
|
-
if (runtimes[name]) {
|
|
32
|
-
let msg = `Runtime '${name}' already registered: ${errType}`
|
|
33
|
-
return errors.push(msg)
|
|
34
|
-
}
|
|
35
|
-
if (runtime.build) {
|
|
36
|
-
if (build && build !== runtime.build) {
|
|
37
|
-
errors.push(`Runtime '${name}' cannot set a build directory, as it is already configured to: ${build}`)
|
|
38
|
-
}
|
|
39
|
-
else if (is.bool(runtime.build) ||
|
|
40
|
-
!is.string(runtime.build)) {
|
|
41
|
-
build = 'build'
|
|
42
|
-
}
|
|
43
|
-
else build = runtime.build
|
|
44
|
-
}
|
|
45
|
-
runtimes.runtimes.push(name)
|
|
46
|
-
runtimes[name] = runtime
|
|
47
|
-
})
|
|
22
|
+
var result = fn({ arc: inv._project.arc, inventory: { inv } })
|
|
48
23
|
}
|
|
49
24
|
catch (err) {
|
|
50
|
-
|
|
25
|
+
err.message = `Runtime plugin exception: ${errType}`
|
|
26
|
+
+ `\n` + err.message
|
|
27
|
+
throw err
|
|
51
28
|
}
|
|
29
|
+
// Accept one or more results, then loop through them
|
|
30
|
+
result = is.array(result) ? result : [ result ]
|
|
31
|
+
result.forEach(runtime => {
|
|
32
|
+
let { name, type, baseRuntime } = runtime
|
|
33
|
+
if (!name || !looserName.test(name)) {
|
|
34
|
+
let msg = `Runtime plugin must provide a valid name: ${errType}`
|
|
35
|
+
return errors.push(msg)
|
|
36
|
+
}
|
|
37
|
+
if (!type || !validTypes.includes(type)) {
|
|
38
|
+
let msg = `Runtime plugin must provide a valid type: ${errType}`
|
|
39
|
+
return errors.push(msg)
|
|
40
|
+
}
|
|
41
|
+
if (allRuntimes.includes(name)) {
|
|
42
|
+
let msg = `Runtime name '${name}' is reserved: ${errType}`
|
|
43
|
+
return errors.push(msg)
|
|
44
|
+
}
|
|
45
|
+
if (runtimes[name]) {
|
|
46
|
+
let msg = `Runtime name '${name}' already registered: ${errType}`
|
|
47
|
+
return errors.push(msg)
|
|
48
|
+
}
|
|
49
|
+
if (builtTypes.includes(type)) {
|
|
50
|
+
if (build && runtime.build && build !== runtime.build) {
|
|
51
|
+
return errors.push(`Runtime '${name}' cannot set a build directory, as it is already configured to: ${build}`)
|
|
52
|
+
}
|
|
53
|
+
// Adhere to Postel's Law
|
|
54
|
+
build = 'build'
|
|
55
|
+
if (is.string(runtime.build)) build = runtime.build
|
|
56
|
+
}
|
|
57
|
+
if (type === 'transpiled' && !allRuntimes.includes(baseRuntime)) {
|
|
58
|
+
return errors.push(`Runtime '${name}' must include a valid baseRuntime property corresponding to a valid Lambda runtime (e.g. 'nodejs14.x')`)
|
|
59
|
+
}
|
|
60
|
+
runtimes.runtimes.push(name)
|
|
61
|
+
runtimes[name] = runtime
|
|
62
|
+
})
|
|
52
63
|
})
|
|
53
64
|
return { build, runtimes }
|
|
54
65
|
}
|
|
@@ -7,60 +7,30 @@
|
|
|
7
7
|
var fs = require("fs");
|
|
8
8
|
var path = require("path");
|
|
9
9
|
var os = require("os");
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
var NEWLINE = "\n";
|
|
14
|
-
var RE_INI_KEY_VAL = /^\s*([\w.-]+)\s*=\s*("[^"]*"|'[^']*'|.*?)(\s+#.*)?$/;
|
|
15
|
-
var RE_NEWLINES = /\\n/g;
|
|
16
|
-
var NEWLINES_MATCH = /\r\n|\n|\r/;
|
|
17
|
-
function parse(src, options) {
|
|
18
|
-
const debug = Boolean(options && options.debug);
|
|
19
|
-
const multiline = Boolean(options && options.multiline);
|
|
10
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
11
|
+
function parse(src) {
|
|
20
12
|
const obj = {};
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
if (multiline && (isMultilineDoubleQuoted || isMultilineSingleQuoted)) {
|
|
34
|
-
const quoteChar = isMultilineDoubleQuoted ? '"' : "'";
|
|
35
|
-
val = val.substring(1);
|
|
36
|
-
while (idx++ < lines.length - 1) {
|
|
37
|
-
line = lines[idx];
|
|
38
|
-
end = line.length - 1;
|
|
39
|
-
if (line[end] === quoteChar) {
|
|
40
|
-
val += NEWLINE + line.substring(0, end);
|
|
41
|
-
break;
|
|
42
|
-
}
|
|
43
|
-
val += NEWLINE + line;
|
|
44
|
-
}
|
|
45
|
-
} else if (isSingleQuoted || isDoubleQuoted) {
|
|
46
|
-
val = val.substring(1, end);
|
|
47
|
-
if (isDoubleQuoted) {
|
|
48
|
-
val = val.replace(RE_NEWLINES, NEWLINE);
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
val = val.trim();
|
|
52
|
-
}
|
|
53
|
-
obj[key] = val;
|
|
54
|
-
} else if (debug) {
|
|
55
|
-
const trimmedLine = line.trim();
|
|
56
|
-
if (trimmedLine.length && trimmedLine[0] !== "#") {
|
|
57
|
-
log(`Failed to match key and value when parsing line ${idx + 1}: ${line}`);
|
|
58
|
-
}
|
|
13
|
+
let lines = src.toString();
|
|
14
|
+
lines = lines.replace(/\r\n?/mg, "\n");
|
|
15
|
+
let match;
|
|
16
|
+
while ((match = LINE.exec(lines)) != null) {
|
|
17
|
+
const key = match[1];
|
|
18
|
+
let value = match[2] || "";
|
|
19
|
+
value = value.trim();
|
|
20
|
+
const maybeQuote = value[0];
|
|
21
|
+
value = value.replace(/^(['"])([\s\S]+)\1$/mg, "$2");
|
|
22
|
+
if (maybeQuote === '"') {
|
|
23
|
+
value = value.replace(/\\n/g, "\n");
|
|
24
|
+
value = value.replace(/\\r/g, "\r");
|
|
59
25
|
}
|
|
26
|
+
obj[key] = value;
|
|
60
27
|
}
|
|
61
28
|
return obj;
|
|
62
29
|
}
|
|
63
|
-
function
|
|
30
|
+
function _log(message) {
|
|
31
|
+
console.log(`[dotenv][DEBUG] ${message}`);
|
|
32
|
+
}
|
|
33
|
+
function _resolveHome(envPath) {
|
|
64
34
|
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
65
35
|
}
|
|
66
36
|
function config(options) {
|
|
@@ -68,17 +38,16 @@ function config(options) {
|
|
|
68
38
|
let encoding = "utf8";
|
|
69
39
|
const debug = Boolean(options && options.debug);
|
|
70
40
|
const override = Boolean(options && options.override);
|
|
71
|
-
const multiline = Boolean(options && options.multiline);
|
|
72
41
|
if (options) {
|
|
73
42
|
if (options.path != null) {
|
|
74
|
-
dotenvPath =
|
|
43
|
+
dotenvPath = _resolveHome(options.path);
|
|
75
44
|
}
|
|
76
45
|
if (options.encoding != null) {
|
|
77
46
|
encoding = options.encoding;
|
|
78
47
|
}
|
|
79
48
|
}
|
|
80
49
|
try {
|
|
81
|
-
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding })
|
|
50
|
+
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }));
|
|
82
51
|
Object.keys(parsed).forEach(function(key) {
|
|
83
52
|
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
|
|
84
53
|
process.env[key] = parsed[key];
|
|
@@ -88,9 +57,9 @@ function config(options) {
|
|
|
88
57
|
}
|
|
89
58
|
if (debug) {
|
|
90
59
|
if (override === true) {
|
|
91
|
-
|
|
60
|
+
_log(`"${key}" is already defined in \`process.env\` and WAS overwritten`);
|
|
92
61
|
} else {
|
|
93
|
-
|
|
62
|
+
_log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`);
|
|
94
63
|
}
|
|
95
64
|
}
|
|
96
65
|
}
|
|
@@ -98,7 +67,7 @@ function config(options) {
|
|
|
98
67
|
return { parsed };
|
|
99
68
|
} catch (e) {
|
|
100
69
|
if (debug) {
|
|
101
|
-
|
|
70
|
+
_log(`Failed to load ${dotenvPath} ${e.message}`);
|
|
102
71
|
}
|
|
103
72
|
return { error: e };
|
|
104
73
|
}
|
|
@@ -2,7 +2,7 @@ let { join } = require('path')
|
|
|
2
2
|
let { existsSync, readFileSync } = require('fs')
|
|
3
3
|
let read = require('../../../read')
|
|
4
4
|
let validate = require('../validate')
|
|
5
|
-
let { is } = require('../../../lib')
|
|
5
|
+
let { is, validationPatterns: valid } = require('../../../lib')
|
|
6
6
|
let { parse } = require('./dotenv')
|
|
7
7
|
let { homedir } = require('os')
|
|
8
8
|
|
|
@@ -20,13 +20,11 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
|
20
20
|
let preferences = {}
|
|
21
21
|
|
|
22
22
|
// Populate Architect preferences
|
|
23
|
-
if (prefs.filepath) {
|
|
23
|
+
if (prefs.filepath && prefs.arc) {
|
|
24
24
|
// Ok, this gets a bit hairy
|
|
25
25
|
// Arc outputs an object of nested arrays
|
|
26
26
|
// Basically, construct a pared-down intermediate prefs obj for consumers
|
|
27
27
|
Object.entries(prefs.arc).forEach(([ key, val ]) => {
|
|
28
|
-
// TODO add additional preferences checks and normalization
|
|
29
|
-
|
|
30
28
|
/* istanbul ignore else: Parser should get this, but jic */
|
|
31
29
|
if (!preferences[key]) preferences[key] = {}
|
|
32
30
|
/* istanbul ignore else: Parser should only produce arrays, but jic */
|
|
@@ -51,6 +49,9 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
|
51
49
|
/* istanbul ignore else: Yet another jic */
|
|
52
50
|
if (preferences.env[e]) {
|
|
53
51
|
Object.entries(preferences.env[e]).forEach(([ key, val ]) => {
|
|
52
|
+
if (!valid.envVar.test(key)) {
|
|
53
|
+
errors.push(`Env var '${key}' is invalid, must be [a-zA-Z0-9_]`)
|
|
54
|
+
}
|
|
54
55
|
if (is.array(val)) preferences.env[e][key] = val.join(' ')
|
|
55
56
|
})
|
|
56
57
|
}
|
package/src/defaults/index.js
CHANGED
|
@@ -33,9 +33,9 @@ module.exports = function inventoryDefaults (params = {}) {
|
|
|
33
33
|
defaultFunctionConfig, // Project-level function config
|
|
34
34
|
rootHandler: null, // null | configured | arcStaticAssetProxy | proxy
|
|
35
35
|
env: { // Env vars pulled from:
|
|
36
|
-
local: null, // Local/global prefs or .env
|
|
37
|
-
plugins: null, // Plugins
|
|
38
|
-
aws: null, // SSM
|
|
36
|
+
local: null, // - Local/global prefs or .env
|
|
37
|
+
plugins: null, // - Plugins
|
|
38
|
+
aws: null, // - SSM
|
|
39
39
|
},
|
|
40
40
|
customRuntimes: null, // Runtime plugins
|
|
41
41
|
arc: [], // Raw arc obj
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
let { join } = require('path')
|
|
1
|
+
let { isAbsolute, join } = require('path')
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Get the src (and build) dirs for a Lambda
|
|
@@ -29,8 +29,8 @@ function getLambdaDirs (params, options) {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
function normalizeSrc (cwd, dir) {
|
|
32
|
-
if (
|
|
33
|
-
return dir
|
|
32
|
+
if (isAbsolute(dir)) return dir
|
|
33
|
+
return join(cwd, dir)
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
getLambdaDirs.normalizeSrc = normalizeSrc
|
package/src/lib/index.js
CHANGED
|
@@ -15,6 +15,15 @@ let compiledRuntimes = [ 'compiled', 'transpiled' ]
|
|
|
15
15
|
// `any` must come last for Sandbox route sorting purposes
|
|
16
16
|
let httpMethods = [ 'get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'any' ]
|
|
17
17
|
|
|
18
|
+
let validationPatterns = {
|
|
19
|
+
strictName: /^[a-z][a-z0-9-]+$/,
|
|
20
|
+
looseName: /^[a-z][a-zA-Z0-9-_]+$/,
|
|
21
|
+
looserName: /^[a-z][a-zA-Z0-9-._]+$/,
|
|
22
|
+
veryLooseName: /^[a-zA-Z0-9/\-._]*$/,
|
|
23
|
+
// IEEE 1003.1-2001 does not allow lowercase, so consider this a compromise for the Windows folks in the house
|
|
24
|
+
envVar: /^[a-zA-Z0-9_]+$/,
|
|
25
|
+
}
|
|
26
|
+
|
|
18
27
|
module.exports = {
|
|
19
28
|
asapSrc,
|
|
20
29
|
compiledRuntimes,
|
|
@@ -25,4 +34,5 @@ module.exports = {
|
|
|
25
34
|
mergeEnvVars,
|
|
26
35
|
normalizeSrc: getLambdaDirs.normalizeSrc,
|
|
27
36
|
pragmas,
|
|
37
|
+
validationPatterns,
|
|
28
38
|
}
|
package/src/validate/index.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
let config = require('./config')
|
|
2
2
|
let layers = require('./layers')
|
|
3
3
|
let tablesChildren = require('./tables-children')
|
|
4
|
-
let
|
|
4
|
+
let paths = require('./paths')
|
|
5
|
+
let { errorFmt } = require('../lib')
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Final inventory validation
|
|
@@ -11,26 +12,32 @@ module.exports = function finalValidation (params, inventory) {
|
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Deal with vendor configuration errors
|
|
15
|
+
* - Analyze function configuration
|
|
16
|
+
* - Ensure layer configuration will work, AWS blows up with awful errors on this
|
|
17
|
+
* - TODO add deeper policy validation
|
|
14
18
|
*/
|
|
15
|
-
// Analyze function configuration
|
|
16
19
|
config(params, inventory, errors)
|
|
17
|
-
|
|
18
|
-
// Ensure layer configuration will work, AWS blows up with awful errors on this
|
|
19
20
|
layers(params, inventory, errors)
|
|
20
|
-
|
|
21
|
-
// TODO add deeper policy validation here
|
|
22
|
-
|
|
23
21
|
if (errors.length) {
|
|
24
22
|
return errorFmt({ type: 'configuration', errors })
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
/**
|
|
28
26
|
* Deal with project validation errors
|
|
27
|
+
* - Ensure @tables children (@tables-streams, @tables-indexes) have parent tables present
|
|
29
28
|
*/
|
|
30
|
-
// Ensure @tables children (@tables-streams, @tables-indexes) have parent tables present
|
|
31
29
|
tablesChildren(inventory, errors)
|
|
32
|
-
|
|
33
30
|
if (errors.length) {
|
|
34
31
|
return errorFmt({ type: 'validation', errors })
|
|
35
32
|
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* File path validation
|
|
36
|
+
* - Ensure all file paths are ascii
|
|
37
|
+
*/
|
|
38
|
+
paths(inventory, errors)
|
|
39
|
+
if (errors.length) {
|
|
40
|
+
return errorFmt({ type: 'file path', errors })
|
|
41
|
+
}
|
|
42
|
+
|
|
36
43
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module.exports = function checkFilePaths (inventory, errors) {
|
|
2
|
+
let ascii = /^[ -~]+$/
|
|
3
|
+
let err = str => errors.push(`${str} path must contain only ascii characters`)
|
|
4
|
+
|
|
5
|
+
let { _project: proj } = inventory
|
|
6
|
+
if (!ascii.test(proj.cwd)) return err('Project file')
|
|
7
|
+
if (!ascii.test(proj.src)) return err('Project source')
|
|
8
|
+
if (proj.build && !ascii.test(proj.build)) return err('Build')
|
|
9
|
+
|
|
10
|
+
let lambdas = inventory.lambdasBySrcDir
|
|
11
|
+
if (lambdas){
|
|
12
|
+
Object.values(lambdas).forEach(lambda => {
|
|
13
|
+
let { name, pragma, src } = lambda
|
|
14
|
+
if (!ascii.test(src)) err(`@${pragma} ${name} source`)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
}
|