@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 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.5",
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.4",
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.0",
32
+ "aws-sdk-mock": "~5.6.2",
33
33
  "cross-env": "~7.0.3",
34
- "dotenv": "~14.3.2",
35
- "eslint": "~8.7.0",
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.4.1"
40
+ "tape": "^5.5.0"
41
41
  },
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,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 === '_methods') {
37
- errors.push('Plugin name _methods is reserved, please rename your plugin')
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, { arc, inventory, errors, pragma }) {
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
- let result = fn({ arc, inventory })
18
- if (!result) {
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.plugin = fn.plugin
23
- result.type = fn.type
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
- // Important: if we don't clean up the build prop, many explosions will explode
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 (!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,33 +1,71 @@
1
- let { is } = require('../../../lib')
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
- 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
- })
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 { testing: env, staging: env, production: env }
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
- 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
- })
22
+ var result = fn({ arc: inv._project.arc, inventory: { inv } })
48
23
  }
49
24
  catch (err) {
50
- errors.push(`Runtime plugin '${fn.plugin}' failed: ${err.message}`)
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
- function log(message) {
11
- console.log(`[dotenv][DEBUG] ${message}`);
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
- const lines = src.toString().split(NEWLINES_MATCH);
22
- for (let idx = 0; idx < lines.length; idx++) {
23
- let line = lines[idx];
24
- const keyValueArr = line.match(RE_INI_KEY_VAL);
25
- if (keyValueArr != null) {
26
- const key = keyValueArr[1];
27
- let val = keyValueArr[2] || "";
28
- let end = val.length - 1;
29
- const isDoubleQuoted = val[0] === '"' && val[end] === '"';
30
- const isSingleQuoted = val[0] === "'" && val[end] === "'";
31
- const isMultilineDoubleQuoted = val[0] === '"' && val[end] !== '"';
32
- const isMultilineSingleQuoted = val[0] === "'" && val[end] !== "'";
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 resolveHome(envPath) {
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 = resolveHome(options.path);
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 }), { debug, multiline });
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
- log(`"${key}" is already defined in \`process.env\` and WAS overwritten`);
60
+ _log(`"${key}" is already defined in \`process.env\` and WAS overwritten`);
92
61
  } else {
93
- log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`);
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
- log(`Failed to load ${dotenvPath} ${e.message}`);
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
  }
@@ -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 (!dir.startsWith(cwd)) return join(cwd, dir)
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
  }
@@ -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
+ }