@architect/inventory 3.0.0-RC.1 → 3.0.0-RC.5

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,19 @@
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
+ - Performance improvements to building `inv.shared` + `inv.views`
30
41
  - Improved memory footprint of Inventory object by preserving references in `lambdaSrcDirs`, `lambdasBySrcDir`
31
- - Added `pragma` property to all Lambdas to preserve references
42
+ - Added `pragma` property to all Lambdas to aid in reference preservation
32
43
  - Tidy up order of enumerated properties in each Lambda
44
+ Update CI
45
+ - Stop publishing to the GitHub Package registry
33
46
 
34
47
  ---
35
48
 
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.5",
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,22 +21,23 @@
20
21
  },
21
22
  "license": "Apache-2.0",
22
23
  "dependencies": {
23
- "@architect/asap": "~4.1.0",
24
- "@architect/parser": "~5.0.2",
24
+ "@architect/asap": "~5.0.0-RC.0",
25
+ "@architect/parser": "~6.0.0-RC.0",
25
26
  "@architect/utils": "~3.0.4",
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.0",
32
33
  "cross-env": "~7.0.3",
33
- "eslint": "~8.5.0",
34
+ "dotenv": "~14.3.2",
35
+ "eslint": "~8.7.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.4.1"
39
41
  },
40
42
  "eslintConfig": {
41
43
  "extends": "@architect/eslint-config"
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
  }
@@ -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,33 @@
1
+ let { is } = require('../../../lib')
2
+
3
+ module.exports = function setEnvPlugins (params, project) {
4
+ let { errors, inventory } = params
5
+ let envPlugins = inventory.plugins?._methods?.set?.env
6
+ if (envPlugins?.length) {
7
+ let env = {}
8
+
9
+ // inventory._project is not yet built, so provide as much as we can to plugins for now
10
+ let inv = { ...inventory, _project: project }
11
+ envPlugins.forEach(fn => {
12
+ let errType = `plugin: ${fn.plugin}, method: set.env`
13
+ try {
14
+ let result = fn({ inventory: { inv } })
15
+ if (!is.object(result) || !Object.keys(result).length) {
16
+ return errors.push(`Env plugin returned invalid data, must return an Object with one or more keys + values: ${errType}`)
17
+ }
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
+ })
25
+ }
26
+ catch (err) {
27
+ errors.push(`Runtime plugin '${fn.plugin}' failed: ${err.message}`)
28
+ }
29
+ })
30
+ return { testing: env, staging: env, production: env }
31
+ }
32
+ return inventory._project.env.plugins
33
+ }
@@ -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
  }
@@ -15,7 +15,7 @@ module.exports = function setRuntimePlugins (params, project) {
15
15
  runtimePlugins.forEach(fn => {
16
16
  let errType = `plugin: ${fn.plugin}, method: set.runtimes`
17
17
  try {
18
- let result = fn({ inventory: inv })
18
+ let result = fn({ inventory: { inv } })
19
19
  result = is.array(result) ? result : [ result ]
20
20
  result.forEach(runtime => {
21
21
  // TODO add more validation
@@ -0,0 +1,112 @@
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
+ 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);
20
+ 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
+ }
59
+ }
60
+ }
61
+ return obj;
62
+ }
63
+ function resolveHome(envPath) {
64
+ return envPath[0] === "~" ? path.join(os.homedir(), envPath.slice(1)) : envPath;
65
+ }
66
+ function config(options) {
67
+ let dotenvPath = path.resolve(process.cwd(), ".env");
68
+ let encoding = "utf8";
69
+ const debug = Boolean(options && options.debug);
70
+ const override = Boolean(options && options.override);
71
+ const multiline = Boolean(options && options.multiline);
72
+ if (options) {
73
+ if (options.path != null) {
74
+ dotenvPath = resolveHome(options.path);
75
+ }
76
+ if (options.encoding != null) {
77
+ encoding = options.encoding;
78
+ }
79
+ }
80
+ try {
81
+ const parsed = DotenvModule.parse(fs.readFileSync(dotenvPath, { encoding }), { debug, multiline });
82
+ Object.keys(parsed).forEach(function(key) {
83
+ if (!Object.prototype.hasOwnProperty.call(process.env, key)) {
84
+ process.env[key] = parsed[key];
85
+ } else {
86
+ if (override === true) {
87
+ process.env[key] = parsed[key];
88
+ }
89
+ if (debug) {
90
+ if (override === true) {
91
+ log(`"${key}" is already defined in \`process.env\` and WAS overwritten`);
92
+ } else {
93
+ log(`"${key}" is already defined in \`process.env\` and was NOT overwritten`);
94
+ }
95
+ }
96
+ }
97
+ });
98
+ return { parsed };
99
+ } catch (e) {
100
+ if (debug) {
101
+ log(`Failed to load ${dotenvPath} ${e.message}`);
102
+ }
103
+ return { error: e };
104
+ }
105
+ }
106
+ var DotenvModule = {
107
+ config,
108
+ parse
109
+ };
110
+ module.exports.config = DotenvModule.config;
111
+ module.exports.parse = DotenvModule.parse;
112
+ 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 } = 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,10 +11,16 @@ 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 })
17
+
18
+ if (!prefs.filepath && !hasEnvFile) return null
19
+
20
+ let preferences = {}
21
+
22
+ // Populate Architect preferences
13
23
  if (prefs.filepath) {
14
- let preferences = {}
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
@@ -45,6 +54,7 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
45
54
  if (is.array(val)) preferences.env[e][key] = val.join(' ')
46
55
  })
47
56
  }
57
+ else preferences.env[e] = null
48
58
  })
49
59
  }
50
60
  // Turn Sandbox scripts into commands
@@ -58,16 +68,24 @@ module.exports = function getPrefs ({ scope, inventory, errors }) {
58
68
  })
59
69
 
60
70
  validate(preferences, errors)
71
+ }
61
72
 
62
- return {
63
- preferences: {
64
- ...preferences,
65
- _arc: prefs.arc,
66
- _raw: prefs.raw,
67
- },
68
- preferencesFile: prefs.filepath
73
+ // Populate .env (testing environment only, disables other env vars)
74
+ if (hasEnvFile) {
75
+ let dotenv = parse(readFileSync(envFilepath))
76
+ preferences.env = {
77
+ testing: Object.keys(dotenv).length ? dotenv : null,
78
+ staging: null,
79
+ production: null,
69
80
  }
70
81
  }
71
82
 
72
- return null
83
+ return {
84
+ preferences: {
85
+ ...preferences,
86
+ _arc: prefs.arc,
87
+ _raw: prefs.raw,
88
+ },
89
+ preferencesFile: prefs.filepath
90
+ }
73
91
  }
@@ -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
  }
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
  /**
@@ -19,8 +20,9 @@ module.exports = {
19
20
  compiledRuntimes,
20
21
  errorFmt,
21
22
  getLambdaDirs,
22
- normalizeSrc: getLambdaDirs.normalizeSrc,
23
23
  httpMethods,
24
24
  is,
25
+ mergeEnvVars,
26
+ normalizeSrc: getLambdaDirs.normalizeSrc,
25
27
  pragmas,
26
28
  }
@@ -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))