@architect/inventory 3.0.0-RC.1 → 3.0.0-RC.10
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 +25 -4
- package/package.json +12 -10
- package/readme.md +5 -4
- package/src/config/arc.js +3 -3
- package/src/config/pragmas/plugins.js +9 -4
- 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 +26 -15
- package/src/config/project/plugins/env.js +71 -0
- package/src/config/project/plugins/index.js +2 -0
- package/src/config/project/plugins/runtimes.js +44 -33
- package/src/config/project/prefs/dotenv.js +81 -0
- package/src/config/project/{prefs.js → prefs/index.js} +35 -16
- package/src/config/project/validate/index.js +14 -7
- package/src/defaults/index.js +9 -4
- package/src/env/index.js +76 -61
- package/src/lib/asap-src.js +3 -3
- package/src/lib/get-lambda-dirs.js +3 -3
- package/src/lib/index.js +17 -1
- package/src/lib/merge-env-vars.js +32 -0
- package/src/lib/pragmas.js +1 -0
- package/src/read/index.js +1 -3
- package/src/read/reader.js +1 -1
- package/src/validate/index.js +16 -9
- package/src/validate/paths.js +17 -0
package/changelog.md
CHANGED
|
@@ -6,14 +6,23 @@
|
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
8
|
|
|
9
|
+
- Architect 10 plugin API support! Specifically:
|
|
10
|
+
- `plugins.set.runtimes` - custom runtime support (still in beta)
|
|
11
|
+
- `plugins.set.env` - add environment variables to all Lambdas
|
|
12
|
+
- `plugins.set.events|http|scheduled|tables-streams|ws` - generate or drop in Lambdas in Architect pragmas
|
|
13
|
+
- `plugins.set.customLambdas` - generate or drop in unique Lambdas with custom event sources
|
|
14
|
+
- More below...
|
|
9
15
|
- Added `inv|get.plugins` tree + methods
|
|
10
16
|
- What used to be `plugins` in the plugins beta is now `customLambdas` (see next item)
|
|
11
17
|
- Added `inv|get.customLambdas`
|
|
12
18
|
- Formerly `inv|get.plugins`
|
|
13
19
|
- Added `inv._project.customRuntimes`
|
|
14
|
-
- Added
|
|
20
|
+
- Added low-level support for `build` destinations to runtime plugins that register type `transpiled` or `compiled`
|
|
15
21
|
- Added `handlerModuleSystem` property for `nodejs14.x` Lambdas, with a value of `cjs` or `esm` based on Lambda + Node.js conventions
|
|
16
22
|
- Added `handlerFile` detection for `nodejs14.x` + `deno` Lambdas
|
|
23
|
+
- This will detect the correct handler file on the filesystem, and fall back to a default handler file if none are found (e.g. `index.js` in `nodejs14.x`)
|
|
24
|
+
- Added `inv._arc.deployStage` property, enabling Inventory to be aware of an intended deploy stage; (this property may change, consider it in beta!)
|
|
25
|
+
- Added built-in support for reading `.env` files when enumerating local env var preferences
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
### Changed
|
|
@@ -21,15 +30,27 @@
|
|
|
21
30
|
- Breaking change: changed `_project.src`, added `_project.cwd`, making both the pair significantly more literal and descriptive
|
|
22
31
|
- `_project.src` is now the default source tree folder (eg `$cwd/src`)
|
|
23
32
|
- `_project.cwd` refers to the current working directory of the project
|
|
33
|
+
- Breaking change: `_project.env` is now by default an object populated by three properties: `local`, `plugins`, and `aws`, reflecting the env vars found for each environment
|
|
24
34
|
- Breaking change: AWS region prioritizes a region passed via param over `AWS_REGION` env var; this should realistically have little or no effect in practice
|
|
25
35
|
- Breaking change: legacy `@tables-streams` folders (`src/tables/...` and `src/streams/...`) are now deprecated
|
|
26
36
|
- Existing functions can be simply moved to `src/tables-streams/{name}` (or use a custom `src` property)
|
|
27
37
|
- Breaking change: renamed `lambda.handlerFunction` to `lambda.handlerMethod`
|
|
28
|
-
- Breaking change: prioritize mod.ts|js in Deno
|
|
29
|
-
-
|
|
38
|
+
- Breaking change: prioritize `mod.ts|js` handlers in Deno Lambdas
|
|
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
|
|
41
|
+
- Performance improvements to building `inv.shared` + `inv.views`
|
|
30
42
|
- Improved memory footprint of Inventory object by preserving references in `lambdaSrcDirs`, `lambdasBySrcDir`
|
|
31
|
-
- Added `pragma` property to all Lambdas to
|
|
43
|
+
- Added `pragma` property to all Lambdas to aid in reference preservation
|
|
32
44
|
- Tidy up order of enumerated properties in each Lambda
|
|
45
|
+
Update CI
|
|
46
|
+
- Stop publishing to the GitHub Package registry
|
|
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
|
|
33
54
|
|
|
34
55
|
---
|
|
35
56
|
|
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.10",
|
|
4
4
|
"description": "Architect project resource enumeration utility",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
"test:integration": "cross-env tape 'test/integration/**/*-test.js' | tap-spec",
|
|
10
10
|
"coverage": "nyc --reporter=lcov --reporter=text npm run test:unit",
|
|
11
11
|
"lint": "eslint . --fix",
|
|
12
|
-
"rc": "npm version prerelease --preid RC"
|
|
12
|
+
"rc": "npm version prerelease --preid RC",
|
|
13
|
+
"vendor": "scripts/vendor"
|
|
13
14
|
},
|
|
14
15
|
"engines": {
|
|
15
16
|
"node": ">=14"
|
|
@@ -20,27 +21,28 @@
|
|
|
20
21
|
},
|
|
21
22
|
"license": "Apache-2.0",
|
|
22
23
|
"dependencies": {
|
|
23
|
-
"@architect/asap": "~
|
|
24
|
-
"@architect/parser": "~
|
|
25
|
-
"@architect/utils": "~3.0.
|
|
24
|
+
"@architect/asap": "~5.0.0-RC.0",
|
|
25
|
+
"@architect/parser": "~6.0.0-RC.0",
|
|
26
|
+
"@architect/utils": "~3.1.0-RC.0",
|
|
26
27
|
"lambda-runtimes": "~1.1.1"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@architect/eslint-config": "~2.0.1",
|
|
30
|
-
"aws-sdk": "2.
|
|
31
|
-
"aws-sdk-mock": "~5.
|
|
31
|
+
"aws-sdk": "2.1001.0",
|
|
32
|
+
"aws-sdk-mock": "~5.6.2",
|
|
32
33
|
"cross-env": "~7.0.3",
|
|
33
|
-
"
|
|
34
|
+
"dotenv": "~15.0.0",
|
|
35
|
+
"eslint": "~8.8.0",
|
|
34
36
|
"mock-fs": "~5.1.2",
|
|
35
37
|
"mock-require": "~3.0.3",
|
|
36
38
|
"nyc": "~15.1.0",
|
|
37
39
|
"tap-spec": "^5.0.0",
|
|
38
|
-
"tape": "^5.
|
|
40
|
+
"tape": "^5.5.0"
|
|
39
41
|
},
|
|
40
42
|
"eslintConfig": {
|
|
41
43
|
"extends": "@architect/eslint-config"
|
|
42
44
|
},
|
|
43
|
-
"
|
|
45
|
+
"nyc": {
|
|
44
46
|
"check-coverage": true,
|
|
45
47
|
"branches": 100,
|
|
46
48
|
"lines": 100,
|
package/readme.md
CHANGED
|
@@ -43,7 +43,7 @@ The inventory object contains the entirety of a project's data, including Archit
|
|
|
43
43
|
|
|
44
44
|
Top-level inventory parameters that start with an underscore (e.g. `_arc`, `_project`) denote project metadata or internal diagnostic data; all other parameters represent userland project resources.
|
|
45
45
|
|
|
46
|
-
In a project inventory, `null` values are used as placeholders for known values or options that were not user-defined. The existence of a non-`null` value can be inferred as a user having specifically defined a setting. For example: `arc.http
|
|
46
|
+
In a project inventory, `null` values are used as placeholders for known values or options that were not user-defined. The existence of a non-`null` value can be inferred as a user having specifically defined a setting. For example: `arc.http: null` can be construed as the user having **not** defined an `@http` pragma. This rule has some exceptions:
|
|
47
47
|
|
|
48
48
|
- A handful of settings that must be backfilled if not supplied
|
|
49
49
|
- Example: `inv.aws.region`, which is required by the `aws-sdk` to function, and will be backfilled if not defined
|
|
@@ -51,9 +51,10 @@ In a project inventory, `null` values are used as placeholders for known values
|
|
|
51
51
|
- Example: while `@static` can be defined on its own without any other pragmas, the existence of `@http` infers `@static`
|
|
52
52
|
- Thus, the act of adding `@http` will necessarily make `inv.static` non-`null`
|
|
53
53
|
- Settings that generate related resources
|
|
54
|
-
- Example: DynamoDB streams can be defined in `@tables` with `stream true`; Inventory would interpret a table with `stream true` as a new `inv
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
- Example: DynamoDB streams can be defined in `@tables` with `stream true`; Inventory would interpret a table with `stream true` as a new `inv['tables-streams']` resource and thus make `inv['tables-streams']` non-`null`
|
|
55
|
+
- Lambda `handlerFile` file path property is present even if the file is not
|
|
56
|
+
- This differs from Lambda `configFile` file path properties, which will be `null` if no file is present
|
|
57
|
+
- This exception is namely because some workflows may need the computed default handler path (example: when running `arc create`)
|
|
57
58
|
|
|
58
59
|
|
|
59
60
|
#### `get`
|
package/src/config/arc.js
CHANGED
|
@@ -6,14 +6,14 @@ let { join } = require('path')
|
|
|
6
6
|
*/
|
|
7
7
|
module.exports = function getArcConfig (params) {
|
|
8
8
|
let { cwd, inventory } = params
|
|
9
|
-
let
|
|
9
|
+
let _arc = { ...inventory._arc }
|
|
10
10
|
|
|
11
11
|
// Version
|
|
12
12
|
let installed = join(cwd, 'node_modules', '@architect', 'architect', 'package.json')
|
|
13
13
|
if (existsSync(installed)) {
|
|
14
14
|
let { version } = JSON.parse(readFileSync(installed))
|
|
15
|
-
|
|
15
|
+
_arc.version = version
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
return
|
|
18
|
+
return _arc
|
|
19
19
|
}
|
|
@@ -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, tidyError, 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) {
|
|
@@ -86,7 +91,7 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
|
|
|
86
91
|
})
|
|
87
92
|
}
|
|
88
93
|
catch (err) {
|
|
89
|
-
errors.push(`Unable to load plugin '${name}': ${err
|
|
94
|
+
errors.push(`Unable to load plugin '${name}': ${tidyError(err)}`)
|
|
90
95
|
}
|
|
91
96
|
}
|
|
92
97
|
else errors.push(`Cannot find plugin '${name || plugin}'! Are you sure you have installed or created it correctly?`)
|
|
@@ -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)
|
|
@@ -2,25 +2,25 @@ let { join } = require('path')
|
|
|
2
2
|
let upsert = require('../_upsert')
|
|
3
3
|
let prefs = require('./prefs')
|
|
4
4
|
let plugins = require('./plugins')
|
|
5
|
-
let { is } = require('../../lib')
|
|
5
|
+
let { is, mergeEnvVars } = require('../../lib')
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Get the project-level configuration, overlaying arc.aws settings (if present)
|
|
9
9
|
*/
|
|
10
10
|
module.exports = function getProjectConfig (params) {
|
|
11
11
|
let { arc, errors, raw, filepath, inventory } = params
|
|
12
|
-
let
|
|
12
|
+
let _project = {
|
|
13
13
|
...inventory._project,
|
|
14
14
|
arc,
|
|
15
15
|
raw,
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
if (arc.aws) {
|
|
19
|
-
|
|
19
|
+
_project.defaultFunctionConfig = upsert(_project.defaultFunctionConfig, arc.aws)
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
if (filepath) {
|
|
23
|
-
|
|
23
|
+
_project.manifest = filepath
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
// Parse local and global project preferences
|
|
@@ -29,37 +29,48 @@ module.exports = function getProjectConfig (params) {
|
|
|
29
29
|
let p = prefs({ scope, inventory, errors })
|
|
30
30
|
if (p) {
|
|
31
31
|
// Set up the scoped metadata
|
|
32
|
-
|
|
33
|
-
|
|
32
|
+
_project[`${scope}Preferences`] = p.preferences
|
|
33
|
+
_project[`${scope}PreferencesFile`] = p.preferencesFile
|
|
34
34
|
|
|
35
35
|
// Build out the final preferences
|
|
36
36
|
/* istanbul ignore else: jic */
|
|
37
|
-
if (!
|
|
37
|
+
if (!_project.preferences) _project.preferences = {}
|
|
38
38
|
Object.keys(p.preferences).forEach(pragma => {
|
|
39
39
|
// Ignore the raw data
|
|
40
40
|
if (pragma === '_arc' || pragma === '_raw') return
|
|
41
41
|
// Allow booleans, etc.
|
|
42
42
|
if (!is.object(p.preferences[pragma])) {
|
|
43
|
-
|
|
43
|
+
_project.preferences[pragma] = p.preferences[pragma]
|
|
44
44
|
return
|
|
45
45
|
}
|
|
46
46
|
// Traverse and merge individual settings
|
|
47
47
|
/* istanbul ignore else: jic */
|
|
48
|
-
if (!
|
|
48
|
+
if (!_project.preferences[pragma]) _project.preferences[pragma] = {}
|
|
49
49
|
Object.entries(p.preferences[pragma]).forEach(([ setting, value ]) => {
|
|
50
|
-
|
|
50
|
+
_project.preferences[pragma][setting] = value
|
|
51
51
|
})
|
|
52
52
|
})
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Populate local env from preferences
|
|
57
|
+
if (_project.preferences?.env) {
|
|
58
|
+
_project.env.local = _project.preferences.env
|
|
59
|
+
}
|
|
60
|
+
|
|
56
61
|
if (inventory.plugins?._methods) {
|
|
57
|
-
|
|
62
|
+
_project.env.plugins = plugins.env(params, _project)
|
|
63
|
+
_project.env.local = mergeEnvVars({
|
|
64
|
+
name: 'Local',
|
|
65
|
+
source: _project.env.plugins,
|
|
66
|
+
target: _project.env.local,
|
|
67
|
+
errors,
|
|
68
|
+
})
|
|
58
69
|
|
|
59
|
-
let { build, runtimes } = plugins.runtimes(params,
|
|
60
|
-
if (build)
|
|
61
|
-
if (runtimes)
|
|
70
|
+
let { build, runtimes } = plugins.runtimes(params, _project)
|
|
71
|
+
if (build) _project.build = join(_project.cwd, build)
|
|
72
|
+
if (runtimes) _project.customRuntimes = runtimes
|
|
62
73
|
}
|
|
63
74
|
|
|
64
|
-
return
|
|
75
|
+
return _project
|
|
65
76
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
let { deepFrozenCopy } = require('@architect/utils')
|
|
2
|
+
let { is, tidyError, 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
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = function setEnvPlugins (params, project) {
|
|
10
|
+
let { errors, inventory } = params
|
|
11
|
+
let envPlugins = inventory.plugins?._methods?.set?.env
|
|
12
|
+
if (envPlugins?.length) {
|
|
13
|
+
let env = {
|
|
14
|
+
testing: null,
|
|
15
|
+
staging: null,
|
|
16
|
+
production: null,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// inventory._project is not yet built, so provide as much as we can to plugins for now
|
|
20
|
+
let inv = deepFrozenCopy({ ...inventory, _project: project })
|
|
21
|
+
envPlugins.forEach(fn => {
|
|
22
|
+
let errType = `plugin: ${fn.plugin}, method: set.env`
|
|
23
|
+
try {
|
|
24
|
+
let result = fn({ arc: inv._project.arc, inventory: { inv } })
|
|
25
|
+
if (!is.object(result) || !Object.keys(result).length) {
|
|
26
|
+
return errors.push(`Env plugin returned invalid data, must return an Object with one or more keys + values: ${errType}`)
|
|
27
|
+
}
|
|
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
|
+
}
|
|
63
|
+
}
|
|
64
|
+
catch (err) {
|
|
65
|
+
errors.push(`Runtime plugin '${fn.plugin}' failed: ${tidyError(err)}`)
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
return env
|
|
69
|
+
}
|
|
70
|
+
return inventory._project.env.plugins
|
|
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
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// Copyright (c) 2015, Scott Motte
|
|
2
|
+
// All rights reserved.
|
|
3
|
+
|
|
4
|
+
/* istanbul ignore file */
|
|
5
|
+
/* eslint-disable */
|
|
6
|
+
// node_modules/dotenv/lib/main.js
|
|
7
|
+
var fs = require("fs");
|
|
8
|
+
var path = require("path");
|
|
9
|
+
var os = require("os");
|
|
10
|
+
var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
|
|
11
|
+
function parse(src) {
|
|
12
|
+
const obj = {};
|
|
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");
|
|
25
|
+
}
|
|
26
|
+
obj[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return obj;
|
|
29
|
+
}
|
|
30
|
+
function _log(message) {
|
|
31
|
+
console.log(`[dotenv][DEBUG] ${message}`);
|
|
32
|
+
}
|
|
33
|
+
function _resolveHome(envPath) {
|
|
34
|
+
return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
|
|
35
|
+
}
|
|
36
|
+
function config(options) {
|
|
37
|
+
let dotenvPath = path.resolve(process.cwd(), ".env");
|
|
38
|
+
let encoding = "utf8";
|
|
39
|
+
const debug = Boolean(options && options.debug);
|
|
40
|
+
const override = Boolean(options && options.override);
|
|
41
|
+
if (options) {
|
|
42
|
+
if (options.path != null) {
|
|
43
|
+
dotenvPath = _resolveHome(options.path);
|
|
44
|
+
}
|
|
45
|
+
if (options.encoding != null) {
|
|
46
|
+
encoding = options.encoding;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
try {
|
|
50
|
+
const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }));
|
|
51
|
+
Object.keys(parsed).forEach(function(key) {
|
|
52
|
+
if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
|
|
53
|
+
process.env[key] = parsed[key];
|
|
54
|
+
} else {
|
|
55
|
+
if (override === true) {
|
|
56
|
+
process.env[key] = parsed[key];
|
|
57
|
+
}
|
|
58
|
+
if (debug) {
|
|
59
|
+
if (override === true) {
|
|
60
|
+
_log(`"${key}" is already defined in \`process.env\` and WAS overwritten`);
|
|
61
|
+
} else {
|
|
62
|
+
_log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return { parsed };
|
|
68
|
+
} catch (e) {
|
|
69
|
+
if (debug) {
|
|
70
|
+
_log(`Failed to load ${dotenvPath} ${e.message}`);
|
|
71
|
+
}
|
|
72
|
+
return { error: e };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
var DotenvModule = {
|
|
76
|
+
config,
|
|
77
|
+
parse
|
|
78
|
+
};
|
|
79
|
+
module.exports.config = DotenvModule.config;
|
|
80
|
+
module.exports.parse = DotenvModule.parse;
|
|
81
|
+
module.exports = DotenvModule;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
let
|
|
2
|
-
let
|
|
3
|
-
let
|
|
1
|
+
let { join } = require('path')
|
|
2
|
+
let { existsSync, readFileSync } = require('fs')
|
|
3
|
+
let read = require('../../../read')
|
|
4
|
+
let validate = require('../validate')
|
|
5
|
+
let { is, validationPatterns: valid } = require('../../../lib')
|
|
6
|
+
let { parse } = require('./dotenv')
|
|
4
7
|
let { homedir } = require('os')
|
|
5
8
|
|
|
6
9
|
module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
@@ -8,16 +11,20 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
|
8
11
|
? homedir()
|
|
9
12
|
: inventory._project.cwd
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
let envFilepath = join(cwd, '.env')
|
|
15
|
+
let hasEnvFile = scope === 'local' && existsSync(envFilepath)
|
|
12
16
|
let prefs = read({ type: 'preferences', cwd, errors })
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
|
|
18
|
+
if (!prefs.filepath && !hasEnvFile) return null
|
|
19
|
+
|
|
20
|
+
let preferences = {}
|
|
21
|
+
|
|
22
|
+
// Populate Architect preferences
|
|
23
|
+
if (prefs.filepath && prefs.arc) {
|
|
15
24
|
// Ok, this gets a bit hairy
|
|
16
25
|
// Arc outputs an object of nested arrays
|
|
17
26
|
// Basically, construct a pared-down intermediate prefs obj for consumers
|
|
18
27
|
Object.entries(prefs.arc).forEach(([ key, val ]) => {
|
|
19
|
-
// TODO add additional preferences checks and normalization
|
|
20
|
-
|
|
21
28
|
/* istanbul ignore else: Parser should get this, but jic */
|
|
22
29
|
if (!preferences[key]) preferences[key] = {}
|
|
23
30
|
/* istanbul ignore else: Parser should only produce arrays, but jic */
|
|
@@ -42,9 +49,13 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
|
42
49
|
/* istanbul ignore else: Yet another jic */
|
|
43
50
|
if (preferences.env[e]) {
|
|
44
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
|
+
}
|
|
45
55
|
if (is.array(val)) preferences.env[e][key] = val.join(' ')
|
|
46
56
|
})
|
|
47
57
|
}
|
|
58
|
+
else preferences.env[e] = null
|
|
48
59
|
})
|
|
49
60
|
}
|
|
50
61
|
// Turn Sandbox scripts into commands
|
|
@@ -58,16 +69,24 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
|
|
|
58
69
|
})
|
|
59
70
|
|
|
60
71
|
validate(preferences, errors)
|
|
72
|
+
}
|
|
61
73
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
74
|
+
// Populate .env (testing environment only, disables other env vars)
|
|
75
|
+
if (hasEnvFile) {
|
|
76
|
+
let dotenv = parse(readFileSync(envFilepath))
|
|
77
|
+
preferences.env = {
|
|
78
|
+
testing: Object.keys(dotenv).length ? dotenv : null,
|
|
79
|
+
staging: null,
|
|
80
|
+
production: null,
|
|
69
81
|
}
|
|
70
82
|
}
|
|
71
83
|
|
|
72
|
-
return
|
|
84
|
+
return {
|
|
85
|
+
preferences: {
|
|
86
|
+
...preferences,
|
|
87
|
+
_arc: prefs.arc,
|
|
88
|
+
_raw: prefs.raw,
|
|
89
|
+
},
|
|
90
|
+
preferencesFile: prefs.filepath
|
|
91
|
+
}
|
|
73
92
|
}
|
|
@@ -2,12 +2,19 @@ let { is } = require('./../../../lib')
|
|
|
2
2
|
|
|
3
3
|
module.exports = function validatePreferences (preferences, errors) {
|
|
4
4
|
// Env checks
|
|
5
|
-
let { env } = preferences
|
|
6
|
-
if (!env) return
|
|
7
|
-
if (env && !is.object(env)) errors.push(`Invalid preferences setting: @env ${env}`)
|
|
8
|
-
|
|
5
|
+
let { env, sandbox } = preferences
|
|
9
6
|
let envs = [ 'testing', 'staging', 'production' ]
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
7
|
+
|
|
8
|
+
if (env && !is.object(env)) {
|
|
9
|
+
errors.push(`Invalid preferences setting: @env ${env}`)
|
|
10
|
+
}
|
|
11
|
+
else if (env) {
|
|
12
|
+
envs.forEach(e => {
|
|
13
|
+
if (env[e] && !is.object(env[e])) errors.push(`Invalid preferences setting: @env ${e}`)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (sandbox?.env && !envs.includes(sandbox.env)) {
|
|
18
|
+
errors.push(`Invalid preferences setting: @sandbox env ${sandbox.env}`)
|
|
19
|
+
}
|
|
13
20
|
}
|
package/src/defaults/index.js
CHANGED
|
@@ -7,7 +7,7 @@ let { pragmas } = require('../lib')
|
|
|
7
7
|
* - Every possible officially supported value should be present
|
|
8
8
|
*/
|
|
9
9
|
module.exports = function inventoryDefaults (params = {}) {
|
|
10
|
-
let { cwd = process.cwd(), region } = params
|
|
10
|
+
let { cwd = process.cwd(), deployStage = null, region } = params
|
|
11
11
|
// Allow region env var override
|
|
12
12
|
region = region || process.env.AWS_REGION || 'us-west-2'
|
|
13
13
|
let defaultFunctionConfig = fnConfig()
|
|
@@ -16,7 +16,8 @@ module.exports = function inventoryDefaults (params = {}) {
|
|
|
16
16
|
_arc: {
|
|
17
17
|
version: 'Unknown', // @architect/architect semver (if installed)
|
|
18
18
|
defaultFunctionConfig, // Architect's default function config
|
|
19
|
-
pragmas, // Registry of all
|
|
19
|
+
pragmas, // Registry of all, Lambda, reserved, retired pragmas
|
|
20
|
+
deployStage, // Deploy stage of the running project (if specified)
|
|
20
21
|
},
|
|
21
22
|
_project: {
|
|
22
23
|
type: 'aws',
|
|
@@ -24,14 +25,18 @@ module.exports = function inventoryDefaults (params = {}) {
|
|
|
24
25
|
src: join(cwd, 'src'), // Default source tree dir
|
|
25
26
|
build: null, // Optional build artifact dir
|
|
26
27
|
manifest: null, // Root project manifest filename
|
|
27
|
-
preferences: null, // Realized preferences obj, resolved from
|
|
28
|
+
preferences: null, // Realized preferences obj, resolved from local > global
|
|
28
29
|
localPreferences: null, // Local preferences obj
|
|
29
30
|
localPreferencesFile: null, // Local preferences file path
|
|
30
31
|
globalPreferences: null, // Global preferences obj
|
|
31
32
|
globalPreferencesFile: null, // Global preferences file path
|
|
32
33
|
defaultFunctionConfig, // Project-level function config
|
|
33
34
|
rootHandler: null, // null | configured | arcStaticAssetProxy | proxy
|
|
34
|
-
env:
|
|
35
|
+
env: { // Env vars pulled from:
|
|
36
|
+
local: null, // - Local/global prefs or .env
|
|
37
|
+
plugins: null, // - Plugins
|
|
38
|
+
aws: null, // - SSM
|
|
39
|
+
},
|
|
35
40
|
customRuntimes: null, // Runtime plugins
|
|
36
41
|
arc: [], // Raw arc obj
|
|
37
42
|
raw: '', // Raw arc string
|
package/src/env/index.js
CHANGED
|
@@ -1,76 +1,91 @@
|
|
|
1
|
+
let { mergeEnvVars } = require('../lib')
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Read env vars out of SSM
|
|
3
5
|
*/
|
|
4
6
|
module.exports = function env (params, inventory, callback) {
|
|
5
|
-
if (params.env) {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
// eslint-disable-next-line
|
|
9
|
-
var aws = require('aws-sdk')
|
|
10
|
-
}
|
|
11
|
-
catch (err) {
|
|
12
|
-
let msg = `'aws-sdk' not found, please install locally or globally (see also readme#aws-sdk-caveat)`
|
|
13
|
-
return callback(Error(msg))
|
|
14
|
-
}
|
|
15
|
-
let name = inventory.app
|
|
16
|
-
let { region } = inventory.aws
|
|
17
|
-
let ssm = new aws.SSM({ region })
|
|
18
|
-
let result = []
|
|
19
|
-
|
|
20
|
-
function getSomeEnvVars (name, NextToken, callback) {
|
|
21
|
-
// Base query to ssm
|
|
22
|
-
let query = {
|
|
23
|
-
Path: `/${name}`,
|
|
24
|
-
Recursive: true,
|
|
25
|
-
MaxResults: 10,
|
|
26
|
-
WithDecryption: true
|
|
27
|
-
}
|
|
7
|
+
if (!params.env) {
|
|
8
|
+
return callback()
|
|
9
|
+
}
|
|
28
10
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
11
|
+
/* istanbul ignore next */
|
|
12
|
+
try {
|
|
13
|
+
// eslint-disable-next-line
|
|
14
|
+
var aws = require('aws-sdk')
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
let msg = `'aws-sdk' not found, please install locally or globally (see also readme#aws-sdk-caveat)`
|
|
18
|
+
return callback(Error(msg))
|
|
19
|
+
}
|
|
20
|
+
let name = inventory.app
|
|
21
|
+
let { region } = inventory.aws
|
|
22
|
+
let ssm = new aws.SSM({ region })
|
|
23
|
+
let result = []
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
app: name, // jic
|
|
42
|
-
env: bits[2],
|
|
43
|
-
name: bits[3],
|
|
44
|
-
value: param.Value,
|
|
45
|
-
}
|
|
46
|
-
}))
|
|
47
|
-
// Check for more data and, if so, recurse
|
|
48
|
-
/* istanbul ignore if: Sadly no way to easily mock this for testing */
|
|
49
|
-
if (data.NextToken) {
|
|
50
|
-
getSomeEnvVars(name, data.NextToken, callback)
|
|
51
|
-
}
|
|
52
|
-
else callback(null, result)
|
|
53
|
-
}
|
|
54
|
-
})
|
|
25
|
+
function getSomeEnvVars (name, NextToken, callback) {
|
|
26
|
+
// Base query to ssm
|
|
27
|
+
let query = {
|
|
28
|
+
Path: `/${name}`,
|
|
29
|
+
Recursive: true,
|
|
30
|
+
MaxResults: 10,
|
|
31
|
+
WithDecryption: true
|
|
55
32
|
}
|
|
56
33
|
|
|
57
|
-
|
|
34
|
+
// Check if we're paginating
|
|
35
|
+
/* istanbul ignore if */
|
|
36
|
+
if (NextToken) query.NextToken = NextToken
|
|
37
|
+
|
|
38
|
+
// Perform the query
|
|
39
|
+
ssm.getParametersByPath(query, function _query (err, data) {
|
|
58
40
|
if (err) callback(err)
|
|
59
41
|
else {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
let
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
|
|
42
|
+
// Tidy up the response
|
|
43
|
+
result = result.concat(data.Parameters.map(function (param) {
|
|
44
|
+
let bits = param.Name.split('/')
|
|
45
|
+
return {
|
|
46
|
+
app: name, // jic
|
|
47
|
+
env: bits[2],
|
|
48
|
+
name: bits[3],
|
|
49
|
+
value: param.Value,
|
|
50
|
+
}
|
|
51
|
+
}))
|
|
52
|
+
// Check for more data and, if so, recurse
|
|
53
|
+
/* istanbul ignore if: Sadly no way to easily mock this for testing */
|
|
54
|
+
if (data.NextToken) {
|
|
55
|
+
getSomeEnvVars(name, data.NextToken, callback)
|
|
70
56
|
}
|
|
71
|
-
callback()
|
|
57
|
+
else callback(null, result)
|
|
72
58
|
}
|
|
73
59
|
})
|
|
74
60
|
}
|
|
75
|
-
|
|
61
|
+
|
|
62
|
+
getSomeEnvVars(name, false, function done (err, result) {
|
|
63
|
+
if (err) callback(err)
|
|
64
|
+
else {
|
|
65
|
+
let testing = null
|
|
66
|
+
let staging = null
|
|
67
|
+
let production = null
|
|
68
|
+
if (result.length) {
|
|
69
|
+
// TODO refactor into a reducer?
|
|
70
|
+
result.forEach(({ env, name: k, value: v }) => {
|
|
71
|
+
if (env === 'testing') testing = Object.assign({}, testing, { [k]: v })
|
|
72
|
+
if (env === 'staging') staging = Object.assign({}, staging, { [k]: v })
|
|
73
|
+
if (env === 'production') production = Object.assign({}, production, { [k]: v })
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let errors = []
|
|
78
|
+
inventory._project.env.aws = mergeEnvVars({
|
|
79
|
+
env: 'Application',
|
|
80
|
+
source: inventory._project.env.plugins,
|
|
81
|
+
target: { testing, staging, production },
|
|
82
|
+
errors,
|
|
83
|
+
})
|
|
84
|
+
if (errors.length) {
|
|
85
|
+
callback(Error(errors[0]))
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
callback()
|
|
89
|
+
}
|
|
90
|
+
})
|
|
76
91
|
}
|
package/src/lib/asap-src.js
CHANGED
|
@@ -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
|
@@ -2,6 +2,7 @@ let asapSrc = require('./asap-src')
|
|
|
2
2
|
let errorFmt = require('./error-fmt')
|
|
3
3
|
let getLambdaDirs = require('./get-lambda-dirs')
|
|
4
4
|
let is = require('./is')
|
|
5
|
+
let mergeEnvVars = require('./merge-env-vars')
|
|
5
6
|
let pragmas = require('./pragmas')
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -14,13 +15,28 @@ let compiledRuntimes = [ 'compiled', 'transpiled' ]
|
|
|
14
15
|
// `any` must come last for Sandbox route sorting purposes
|
|
15
16
|
let httpMethods = [ 'get', 'post', 'put', 'patch', 'delete', 'options', 'head', 'any' ]
|
|
16
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
|
+
|
|
27
|
+
// Error tidier: remove the error name, just print the message + stack data
|
|
28
|
+
let tidyError = err => err.message + `\n` + err.stack.split('\n').slice(1).join('\n')
|
|
29
|
+
|
|
17
30
|
module.exports = {
|
|
18
31
|
asapSrc,
|
|
19
32
|
compiledRuntimes,
|
|
20
33
|
errorFmt,
|
|
21
34
|
getLambdaDirs,
|
|
22
|
-
normalizeSrc: getLambdaDirs.normalizeSrc,
|
|
23
35
|
httpMethods,
|
|
24
36
|
is,
|
|
37
|
+
mergeEnvVars,
|
|
38
|
+
normalizeSrc: getLambdaDirs.normalizeSrc,
|
|
25
39
|
pragmas,
|
|
40
|
+
tidyError,
|
|
41
|
+
validationPatterns,
|
|
26
42
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
let envs = [ 'testing', 'staging', 'production' ]
|
|
2
|
+
|
|
3
|
+
module.exports = function mergeEnvVars (params) {
|
|
4
|
+
let { name, source, target, errors } = params
|
|
5
|
+
if (source === null) return target
|
|
6
|
+
if (target === null) return source
|
|
7
|
+
let probs = []
|
|
8
|
+
|
|
9
|
+
// Deep copy to reset any potential refs
|
|
10
|
+
let merged = JSON.parse(JSON.stringify(target))
|
|
11
|
+
envs.forEach(env => {
|
|
12
|
+
if (!source[env]) return
|
|
13
|
+
Object.keys(source[env]).forEach(k => {
|
|
14
|
+
if (merged[env]?.[k]) {
|
|
15
|
+
probs.push(`'${env}' variable '${k}'`)
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
if (!merged[env]) merged[env] = {}
|
|
19
|
+
merged[env][k] = source[env][k]
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
if (probs.length) {
|
|
25
|
+
let s = probs.length > 1 ? 's' : ''
|
|
26
|
+
let msg = `${name} env var${s} conflicts with plugin:\n` +
|
|
27
|
+
`- ${probs.join('\n- ')}`
|
|
28
|
+
errors.push(msg)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return merged
|
|
32
|
+
}
|
package/src/lib/pragmas.js
CHANGED
package/src/read/index.js
CHANGED
|
@@ -5,7 +5,6 @@ let projectManifest = {
|
|
|
5
5
|
arc: [ 'app.arc', '.arc' ],
|
|
6
6
|
json: [ 'arc.json' ],
|
|
7
7
|
yaml: [ 'arc.yaml', 'arc.yml' ],
|
|
8
|
-
toml: [ 'arc.toml' ],
|
|
9
8
|
manifest: [ 'package.json' ],
|
|
10
9
|
_default: `@app\napp-default\n@http\n@static`,
|
|
11
10
|
}
|
|
@@ -15,13 +14,12 @@ let functionConfig = {
|
|
|
15
14
|
arc: [ 'config.arc', '.arc-config' ],
|
|
16
15
|
json: [ 'arc.json', 'arc-config.json' ],
|
|
17
16
|
yaml: [ 'config.yaml', 'config.yml', 'arc-config.yaml', 'arc-config.yml' ],
|
|
18
|
-
toml: [ 'config.toml', 'arc-config.toml' ],
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
// Local preferences
|
|
22
20
|
let preferences = {
|
|
23
21
|
arc: [ 'preferences.arc', 'prefs.arc', '.preferences.arc', '.prefs.arc', ],
|
|
24
|
-
// TODO add json, yaml
|
|
22
|
+
// TODO add json, yaml later if folks want it?
|
|
25
23
|
}
|
|
26
24
|
|
|
27
25
|
let reads = { projectManifest, functionConfig, preferences }
|
package/src/read/reader.js
CHANGED
|
@@ -37,7 +37,7 @@ module.exports = function reader (reads, cwd, errors) {
|
|
|
37
37
|
if (raw.trim() === '') return errors.push(`Empty file: ${f}`)
|
|
38
38
|
arc = type === 'arc'
|
|
39
39
|
? parse(raw)
|
|
40
|
-
: parse[type](raw) // Parser has convenient json
|
|
40
|
+
: parse[type](raw) // Parser has convenient json + yaml methods!
|
|
41
41
|
}
|
|
42
42
|
else {
|
|
43
43
|
let pkg = JSON.parse(read(file))
|
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
|
+
}
|