@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 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 the concept of a `build` destination (currently only available via plugin)
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
- - Internal change: performance improvements to building `inv.shared` + `inv.views`
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 preserve references
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.1",
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": "~4.1.0",
24
- "@architect/parser": "~5.0.2",
25
- "@architect/utils": "~3.0.4",
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.880.0",
31
- "aws-sdk-mock": "~5.5.0",
31
+ "aws-sdk": "2.1001.0",
32
+ "aws-sdk-mock": "~5.6.2",
32
33
  "cross-env": "~7.0.3",
33
- "eslint": "~8.5.0",
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.3.2"
40
+ "tape": "^5.5.0"
39
41
  },
40
42
  "eslintConfig": {
41
43
  "extends": "@architect/eslint-config"
42
44
  },
43
- "_nyc": {
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 === null` can be construed as the user having **not** defined an `@http` pragma. This rule has some exceptions:
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.streams` resource and thus make `inv.streams` non-`null`
55
-
56
- > Note: The `inv` format is primarily designed and intended for internal use within Architect libraries; as such, Inventory may theoretically introduce breaking changes in its behavior, shape, or naming conventions.
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 arc = { ...inventory._arc }
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
- arc.version = version
15
+ _arc.version = version
16
16
  }
17
17
 
18
- return arc
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 === '_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) {
@@ -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.message.split('\n')[0]}`)
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, { 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)
@@ -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 project = {
12
+ let _project = {
13
13
  ...inventory._project,
14
14
  arc,
15
15
  raw,
16
16
  }
17
17
 
18
18
  if (arc.aws) {
19
- project.defaultFunctionConfig = upsert(project.defaultFunctionConfig, arc.aws)
19
+ _project.defaultFunctionConfig = upsert(_project.defaultFunctionConfig, arc.aws)
20
20
  }
21
21
 
22
22
  if (filepath) {
23
- project.manifest = filepath
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
- project[`${scope}Preferences`] = p.preferences
33
- project[`${scope}PreferencesFile`] = p.preferencesFile
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 (!project.preferences) project.preferences = {}
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
- project.preferences[pragma] = p.preferences[pragma]
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 (!project.preferences[pragma]) project.preferences[pragma] = {}
48
+ if (!_project.preferences[pragma]) _project.preferences[pragma] = {}
49
49
  Object.entries(p.preferences[pragma]).forEach(([ setting, value ]) => {
50
- project.preferences[pragma][setting] = value
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
- // TODO: project.env = plugins.env(params, project)
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, project)
60
- if (build) project.build = join(project.cwd, build)
61
- if (runtimes) project.customRuntimes = 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 project
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,5 +1,7 @@
1
+ let env = require('./env')
1
2
  let runtimes = require('./runtimes')
2
3
 
3
4
  module.exports = {
5
+ env,
4
6
  runtimes,
5
7
  }
@@ -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
  }
@@ -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 read = require('../../read')
2
- let validate = require('./validate')
3
- let { is } = require('../../lib')
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
- // Populate preferences
14
+ let envFilepath = join(cwd, '.env')
15
+ let hasEnvFile = scope === 'local' && existsSync(envFilepath)
12
16
  let prefs = read({ type: 'preferences', cwd, errors })
13
- if (prefs.filepath) {
14
- let preferences = {}
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
- return {
63
- preferences: {
64
- ...preferences,
65
- _arc: prefs.arc,
66
- _raw: prefs.raw,
67
- },
68
- preferencesFile: prefs.filepath
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 null
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
- envs.forEach(e => {
11
- if (env[e] && !is.object(env[e])) errors.push(`Invalid preferences setting: @env ${e}`)
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
  }
@@ -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 + Lambda pragmas
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 global > local
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: null, // Env vars pulled from SSM + plugins
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
- /* istanbul ignore next */
7
- try {
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
- // Check if we're paginating
30
- /* istanbul ignore if */
31
- if (NextToken) query.NextToken = NextToken
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
- // Perform the query
34
- ssm.getParametersByPath(query, function _query (err, data) {
35
- if (err) callback(err)
36
- else {
37
- // Tidy up the response
38
- result = result.concat(data.Parameters.map(function (param) {
39
- let bits = param.Name.split('/')
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
- getSomeEnvVars(name, false, function done (err, result) {
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
- if (result.length) {
61
- let testing = null
62
- let staging = null
63
- let production = null
64
- result.forEach(r => {
65
- if (r.env === 'testing') testing = Object.assign({}, testing, { [r.name]: r.value })
66
- if (r.env === 'staging') staging = Object.assign({}, staging, { [r.name]: r.value })
67
- if (r.env === 'production') production = Object.assign({}, production, { [r.name]: r.value })
68
- })
69
- inventory._project.env = { testing, staging, production }
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
- else callback()
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
  }
@@ -17,7 +17,7 @@ module.exports = function asapSrc () {
17
17
  try {
18
18
  return require.resolve('@architect/asap')
19
19
  }
20
- catch (err) { /* Swallow */ }
21
-
22
- throw Error('Cannot find ASAP module!')
20
+ catch (err) {
21
+ throw Error('Cannot find ASAP module!')
22
+ }
23
23
  }
@@ -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
@@ -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
+ }
@@ -36,6 +36,7 @@ module.exports = {
36
36
  'indexes',
37
37
  'macros',
38
38
  'slack',
39
+ 'streams', // Never fully launched
39
40
  // Static types
40
41
  'css',
41
42
  'html',
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, toml later if folks want it?
22
+ // TODO add json, yaml later if folks want it?
25
23
  }
26
24
 
27
25
  let reads = { projectManifest, functionConfig, preferences }
@@ -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, yaml, toml methods!
40
+ : parse[type](raw) // Parser has convenient json + yaml methods!
41
41
  }
42
42
  else {
43
43
  let pkg = JSON.parse(read(file))
@@ -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
+ }