@architect/inventory 3.0.0-RC.6 → 3.0.0-RC.7

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 CHANGED
@@ -44,6 +44,11 @@
44
44
  Update CI
45
45
  - Stop publishing to the GitHub Package registry
46
46
 
47
+
48
+ ### Fixed
49
+
50
+ - Added file path validation because `aws-sdk` blows up on !ascii paths; fixes #1292, thanks @GustMartins!
51
+
47
52
  ---
48
53
 
49
54
  ## [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.6",
3
+ "version": "3.0.0-RC.7",
4
4
  "description": "Architect project resource enumeration utility",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -42,7 +42,7 @@
42
42
  "eslintConfig": {
43
43
  "extends": "@architect/eslint-config"
44
44
  },
45
- "_nyc": {
45
+ "nyc": {
46
46
  "check-coverage": true,
47
47
  "branches": 100,
48
48
  "lines": 100,
@@ -1,6 +1,6 @@
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 ]
@@ -37,6 +37,10 @@ module.exports = function getPluginModules ({ arc, inventory, errors }) {
37
37
  errors.push('Plugin name _methods is reserved, please rename your plugin')
38
38
  continue
39
39
  }
40
+ if (!validationPatterns.veryLooseName.test(name)) {
41
+ errors.push('Plugin names can only contain [a-zA-Z0-9/\\-._]')
42
+ continue
43
+ }
40
44
  if (pluginPath) {
41
45
  try {
42
46
  if (type === 'plugin') {
@@ -1,3 +1,4 @@
1
+ let { sep } = require('path')
1
2
  let read = require('../../../read')
2
3
  let getLambda = require('./get-lambda')
3
4
  let getRuntime = require('./get-runtime')
@@ -9,22 +10,44 @@ let { compiledRuntimes, is } = require('../../../lib')
9
10
  /**
10
11
  * Build out the Lambda tree from the Arc manifest or a passed pragma, and plugins
11
12
  */
12
- function populateLambda (type, { arc, inventory, errors, pragma }) {
13
+ function populateLambda (type, params) {
14
+ // Passing a pragma array via params allows special overrides
15
+ // See: @tables populating inv['tables-streams']
16
+ let { arc, inventory, errors, pragma } = params
17
+
13
18
  let plugins = inventory.plugins?._methods?.set?.[type]
14
19
  let pluginLambda = []
15
20
  if (plugins) {
16
21
  let pluginResults = plugins.flatMap(fn => {
17
- let result = fn({ arc, inventory: { inv: inventory } })
18
- if (!result) {
22
+ try {
23
+ var result = fn({ arc, inventory: { inv: inventory } })
24
+ }
25
+ catch (err) {
26
+ err.message = `Setter plugin exception: plugin: ${fn.plugin}, method: set.${type}`
27
+ + `\n` + err.message
28
+ throw err
29
+ }
30
+ if (!result ||
31
+ (!is.object(result) && !is.array(result)) ||
32
+ (is.array(result) && result.some(r => !is.object(r)))) {
19
33
  errors.push(`Setter plugins must return a valid response: plugin: ${fn.plugin}, method: set.${type}`)
20
34
  return []
21
35
  }
22
- result.plugin = fn.plugin
23
- result.type = fn.type
36
+ if (is.array(result)) {
37
+ result.forEach(item => {
38
+ item.plugin = fn.plugin
39
+ item.type = fn.type
40
+ })
41
+ }
42
+ else {
43
+ result.plugin = fn.plugin
44
+ result.type = fn.type
45
+ }
24
46
  return result
25
47
  })
26
48
  pluginLambda = populate(type, pluginResults, inventory, errors, true) || []
27
49
  }
50
+
28
51
  let pragmaLambda = populate(type, pragma || arc[type], inventory, errors) || []
29
52
  let aggregate = [ ...pluginLambda, ...pragmaLambda ]
30
53
  return aggregate.length ? aggregate : null
@@ -46,6 +69,11 @@ function populate (type, pragma, inventory, errors, plugin) {
46
69
  if (!result) continue
47
70
 
48
71
  let { name, src, build } = result
72
+
73
+ // Normalize paths, especially since plugin authors may not use path.join
74
+ src = normalize(src)
75
+ if (build) build = normalize(build)
76
+
49
77
  // Set up fresh config, then overlay plugin config
50
78
  let config = defaultProjectConfig()
51
79
  config = { ...config, ...getKnownProps(configProps, result.config) }
@@ -82,7 +110,7 @@ function populate (type, pragma, inventory, errors, plugin) {
82
110
 
83
111
  // Tidy up any irrelevant properties
84
112
  if (!compiledRuntimes.includes(config.runtimeConfig?.type)) {
85
- // Important: if we don't clean up the build prop, many explosions will explode
113
+ // Super important! If we don't clean up the build prop, many explosions will explode
86
114
  build = undefined
87
115
  }
88
116
  if (type !== 'http') {
@@ -111,8 +139,10 @@ function populate (type, pragma, inventory, errors, plugin) {
111
139
  return lambdas
112
140
  }
113
141
 
142
+ let normalize = path => path.replace(/[\\\/]/g, sep)
143
+
114
144
  // Lambda setter plugins can technically return anything, so this ensures everything is tidy
115
- let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table' ]
145
+ let lambdaProps = [ 'cron', 'method', 'path', 'plugin', 'rate', 'route', 'table', 'type' ]
116
146
  let configProps = [ ...Object.keys(defaultFunctionConfig()), 'fifo', 'views' ]
117
147
  let getKnownProps = (knownProps, raw = {}) => {
118
148
  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 (!patterns[pattern]) throw ReferenceError(`Invalid validation pattern specified: ${pattern}`)
12
- if (!patterns[pattern].test(value)) errors.push(`Invalid ${pragmaName} item: '${value}' must match ${patterns[pattern]}`)
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].toLowerCase()
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) _project.build = join(_project.cwd, build)
71
+ if (build) _project.build = join(_project.cwd, build)
72
72
  if (runtimes) _project.customRuntimes = runtimes
73
73
  }
74
74
 
@@ -1,10 +1,22 @@
1
1
  let { is } = require('../../../lib')
2
+ let envs = [ 'testing', 'staging', 'production' ]
3
+ let str = value => {
4
+ if (is.object(value) || is.array(value)) return JSON.stringify(value)
5
+ return String(value)
6
+ }
2
7
 
3
8
  module.exports = function setEnvPlugins (params, project) {
4
9
  let { errors, inventory } = params
5
10
  let envPlugins = inventory.plugins?._methods?.set?.env
6
11
  if (envPlugins?.length) {
7
- let env = {}
12
+ let env = {
13
+ testing: null,
14
+ staging: null,
15
+ production: null,
16
+ }
17
+
18
+ // IEEE 1003.1-2001 does not allow lowercase, so consider this a compromise for the Windows folks in the house
19
+ let validName = /^[a-zA-Z0-9_]+$/
8
20
 
9
21
  // inventory._project is not yet built, so provide as much as we can to plugins for now
10
22
  let inv = { ...inventory, _project: project }
@@ -15,19 +27,44 @@ module.exports = function setEnvPlugins (params, project) {
15
27
  if (!is.object(result) || !Object.keys(result).length) {
16
28
  return errors.push(`Env plugin returned invalid data, must return an Object with one or more keys + values: ${errType}`)
17
29
  }
18
- Object.entries(result).forEach(([ k, v ]) => {
19
- if (env[k]) {
20
- return errors.push(`Env var '${k}' already registered: ${errType}`)
21
- }
22
- if (is.object(v) || is.array(v)) env[k] = JSON.stringify(v)
23
- else env[k] = String(v)
24
- })
30
+ // Populate env vars based on environment
31
+ // If any keys are environment names, disregard all keys except environment names
32
+ if (Object.keys(result).some(k => envs.includes(k))) {
33
+ envs.forEach(e => {
34
+ if (result[e]) Object.entries(result[e]).forEach(([ k, v ]) => {
35
+ let errored = false, val = str(v)
36
+ if (!env[e]) env[e] = { [k]: val }
37
+ else if (env[e][k] && !errored) {
38
+ errored = true
39
+ errors.push(`Env var '${k}' already registered: ${errType}`)
40
+ }
41
+ else env[e][k] = val
42
+ })
43
+ })
44
+ }
45
+ // Populate all environments based on env var
46
+ else {
47
+ Object.entries(result).forEach(([ k, v ]) => {
48
+ if (!validName.test(k)) {
49
+ return errors.push(`Env var '${k}' is invalid, must be [a-zA-Z0-9_]`)
50
+ }
51
+ let errored = false, val = str(v)
52
+ envs.forEach(e => {
53
+ if (!env[e]) env[e] = { [k]: val }
54
+ else if (env[e][k] && !errored) {
55
+ errored = true
56
+ errors.push(`Env var '${k}' already registered: ${errType}`)
57
+ }
58
+ else env[e][k] = val
59
+ })
60
+ })
61
+ }
25
62
  }
26
63
  catch (err) {
27
64
  errors.push(`Runtime plugin '${fn.plugin}' failed: ${err.message}`)
28
65
  }
29
66
  })
30
- return { testing: env, staging: env, production: env }
67
+ return env
31
68
  }
32
69
  return inventory._project.env.plugins
33
70
  }
@@ -1,6 +1,9 @@
1
- let { is } = require('../../../lib')
1
+ let { is, validationPatterns } = require('../../../lib')
2
2
  let { aliases, runtimeList } = require('lambda-runtimes')
3
+ let { looserName } = validationPatterns
3
4
  let allRuntimes = runtimeList.concat([ 'deno', ...Object.keys(aliases) ])
5
+ let validTypes = [ 'transpiled', 'compiled', 'interpreted' ]
6
+ let builtTypes = validTypes.filter(t => t !== 'interpreted')
4
7
 
5
8
  module.exports = function setRuntimePlugins (params, project) {
6
9
  let { errors, inventory } = params
@@ -15,40 +18,47 @@ module.exports = function setRuntimePlugins (params, project) {
15
18
  runtimePlugins.forEach(fn => {
16
19
  let errType = `plugin: ${fn.plugin}, method: set.runtimes`
17
20
  try {
18
- let result = fn({ inventory: { inv } })
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
- })
21
+ var result = fn({ inventory: { inv } })
48
22
  }
49
23
  catch (err) {
50
- errors.push(`Runtime plugin '${fn.plugin}' failed: ${err.message}`)
24
+ err.message = `Runtime plugin exception: ${errType}`
25
+ + `\n` + err.message
26
+ throw err
51
27
  }
28
+ // Accept one or more results, then loop through them
29
+ result = is.array(result) ? result : [ result ]
30
+ result.forEach(runtime => {
31
+ let { name, type, baseRuntime } = runtime
32
+ if (!name || !looserName.test(name)) {
33
+ let msg = `Runtime plugin must provide a valid name: ${errType}`
34
+ return errors.push(msg)
35
+ }
36
+ if (!type || !validTypes.includes(type)) {
37
+ let msg = `Runtime plugin must provide a valid type: ${errType}`
38
+ return errors.push(msg)
39
+ }
40
+ if (allRuntimes.includes(name)) {
41
+ let msg = `Runtime name '${name}' is reserved: ${errType}`
42
+ return errors.push(msg)
43
+ }
44
+ if (runtimes[name]) {
45
+ let msg = `Runtime name '${name}' already registered: ${errType}`
46
+ return errors.push(msg)
47
+ }
48
+ if (builtTypes.includes(type)) {
49
+ if (build && runtime.build && build !== runtime.build) {
50
+ return errors.push(`Runtime '${name}' cannot set a build directory, as it is already configured to: ${build}`)
51
+ }
52
+ // Adhere to Postel's Law
53
+ build = 'build'
54
+ if (is.string(runtime.build)) build = runtime.build
55
+ }
56
+ if (type === 'transpiled' && !allRuntimes.includes(baseRuntime)) {
57
+ return errors.push(`Runtime '${name}' must include a valid baseRuntime property corresponding to a valid Lambda runtime (e.g. 'nodejs14.x')`)
58
+ }
59
+ runtimes.runtimes.push(name)
60
+ runtimes[name] = runtime
61
+ })
52
62
  })
53
63
  return { build, runtimes }
54
64
  }
@@ -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
package/src/lib/index.js CHANGED
@@ -15,6 +15,13 @@ 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
+ }
24
+
18
25
  module.exports = {
19
26
  asapSrc,
20
27
  compiledRuntimes,
@@ -25,4 +32,5 @@ module.exports = {
25
32
  mergeEnvVars,
26
33
  normalizeSrc: getLambdaDirs.normalizeSrc,
27
34
  pragmas,
35
+ validationPatterns,
28
36
  }
@@ -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 errorFmt = require('../lib/error-fmt')
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
+ }