@adobe/aio-cli-lib-app-config 0.2.2 → 1.0.1-pre.2023-07-10.sha-32c62844
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/package.json +23 -17
- package/schema/app.config.yaml.schema.json +235 -0
- package/src/index.js +289 -171
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aio-cli-lib-app-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.1-pre.2023-07-10.sha-32c62844",
|
|
4
4
|
"description": "node lib to provide a consistent interface to various config files that make up Adobe Developer App Builder applications and extensions",
|
|
5
5
|
"repository": "https://github.com/adobe/aio-cli-lib-app-config/",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"main": "src/index.js",
|
|
8
8
|
"files": [
|
|
9
|
-
"src"
|
|
9
|
+
"src",
|
|
10
|
+
"schema"
|
|
10
11
|
],
|
|
11
12
|
"scripts": {
|
|
12
13
|
"test": "npm run lint && npm run unit-tests",
|
|
@@ -17,34 +18,38 @@
|
|
|
17
18
|
"generate-docs": "npm run typings && npm run jsdoc"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
20
|
-
"@adobe/aio-lib-core-config": "^
|
|
21
|
-
"@adobe/aio-lib-core-logging": "
|
|
22
|
-
"@adobe/aio-lib-env": "^
|
|
21
|
+
"@adobe/aio-lib-core-config": "^3.0.0",
|
|
22
|
+
"@adobe/aio-lib-core-logging": "next",
|
|
23
|
+
"@adobe/aio-lib-env": "^2.0.0",
|
|
24
|
+
"ajv": "^8.12.0",
|
|
25
|
+
"ajv-formats": "^2.1.1",
|
|
23
26
|
"fs-extra": "^9.0.1",
|
|
24
27
|
"js-yaml": "^3.14.0",
|
|
25
28
|
"lodash.clonedeep": "^4.5.0"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
|
-
"@adobe/eslint-config-aio-lib-config": "^
|
|
31
|
+
"@adobe/eslint-config-aio-lib-config": "^2",
|
|
29
32
|
"babel-runtime": "^6.26.0",
|
|
30
33
|
"eol": "^0.9.1",
|
|
31
|
-
"eslint": "^
|
|
32
|
-
"eslint-config-standard": "^
|
|
33
|
-
"eslint-plugin-import": "^2.
|
|
34
|
-
"eslint-plugin-jest": "^23
|
|
35
|
-
"eslint-plugin-jsdoc": "^
|
|
34
|
+
"eslint": "^8",
|
|
35
|
+
"eslint-config-standard": "^17",
|
|
36
|
+
"eslint-plugin-import": "^2.25.3",
|
|
37
|
+
"eslint-plugin-jest": "^23",
|
|
38
|
+
"eslint-plugin-jsdoc": "^37",
|
|
39
|
+
"eslint-plugin-n": "^15",
|
|
36
40
|
"eslint-plugin-node": "^11.1.0",
|
|
37
|
-
"eslint-plugin-promise": "^
|
|
41
|
+
"eslint-plugin-promise": "^6",
|
|
38
42
|
"eslint-plugin-standard": "^4.0.0",
|
|
39
|
-
"jest": "^
|
|
43
|
+
"jest": "^27",
|
|
40
44
|
"jest-junit": "^10.0.0",
|
|
41
45
|
"jest-plugin-fs": "^2.9.0",
|
|
42
46
|
"jsdoc": "^3.6.3",
|
|
43
47
|
"jsdoc-to-markdown": "^5.0.0",
|
|
44
|
-
"stdout-stderr": "^0.1.9"
|
|
48
|
+
"stdout-stderr": "^0.1.9",
|
|
49
|
+
"typescript": "^4.5.2"
|
|
45
50
|
},
|
|
46
51
|
"engines": {
|
|
47
|
-
"node": "
|
|
52
|
+
"node": "^14.18 || ^16.13 || >=18"
|
|
48
53
|
},
|
|
49
54
|
"jest": {
|
|
50
55
|
"collectCoverage": true,
|
|
@@ -69,5 +74,6 @@
|
|
|
69
74
|
"setupFilesAfterEnv": [
|
|
70
75
|
"./test/jest.setup.js"
|
|
71
76
|
]
|
|
72
|
-
}
|
|
73
|
-
|
|
77
|
+
},
|
|
78
|
+
"prereleaseSha": "32c628440bb4e52c8b3db916d6081c6e94dc1521"
|
|
79
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"$id": "https://adobe.io/schemas/app-builder/app.config.yaml.json/v2",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"properties": {
|
|
6
|
+
"application": { "$ref": "#/definitions/application" },
|
|
7
|
+
"extensions": { "$ref": "#/definitions/extensions" },
|
|
8
|
+
"configSchema": { "$ref": "#/definitions/configSchema"},
|
|
9
|
+
"productDependencies": { "$ref": "#/definitions/productDependencies"}
|
|
10
|
+
},
|
|
11
|
+
"anyOf": [
|
|
12
|
+
{
|
|
13
|
+
"required": ["application"]
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"required": ["extensions"]
|
|
17
|
+
}
|
|
18
|
+
],
|
|
19
|
+
"definitions": {
|
|
20
|
+
"extensions": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"patternProperties": {
|
|
23
|
+
"^[A-Za-z0-9-_/\\-]+$": {
|
|
24
|
+
"$ref": "#/definitions/application",
|
|
25
|
+
"properties": {
|
|
26
|
+
"operations": {
|
|
27
|
+
"type": "object",
|
|
28
|
+
"patternProperties": {
|
|
29
|
+
"^[^\n]+$": {
|
|
30
|
+
"type":"array",
|
|
31
|
+
"items": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"properties": {
|
|
34
|
+
"type": { "type": "string" },
|
|
35
|
+
"impl": { "type": "string" }
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
"minItems": 1
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"minProperties": 1
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": ["operations"]
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"additionalProperties": false
|
|
48
|
+
},
|
|
49
|
+
"application": {
|
|
50
|
+
"type": "object",
|
|
51
|
+
"properties": {
|
|
52
|
+
"runtimeManifest": { "$ref": "#/definitions/runtimeManifest" },
|
|
53
|
+
"actions": { "type": "string" },
|
|
54
|
+
"unitTest": { "type": "string" },
|
|
55
|
+
"e2eTest": { "type": "string" },
|
|
56
|
+
"dist": { "type": "string" },
|
|
57
|
+
"tvmurl": { "type": "string" },
|
|
58
|
+
"awsaccesskeyid": { "type": "string" },
|
|
59
|
+
"awssecretaccesskey": { "type": "string" },
|
|
60
|
+
"s3bucket": { "type": "string" },
|
|
61
|
+
"events": { "$ref": "#/definitions/events" },
|
|
62
|
+
"hostname": { "type": "string" },
|
|
63
|
+
"htmlcacheduration": { "type": "number" },
|
|
64
|
+
"jscacheduration": { "type": "number" },
|
|
65
|
+
"csscacheduration": { "type": "number" },
|
|
66
|
+
"imagecacheduration": { "type": "number" },
|
|
67
|
+
"hooks": { "$ref": "#/definitions/hooks" },
|
|
68
|
+
"web": { "$ref": "#/definitions/web" }
|
|
69
|
+
},
|
|
70
|
+
"required": []
|
|
71
|
+
},
|
|
72
|
+
"web": {
|
|
73
|
+
"anyOf": [
|
|
74
|
+
{
|
|
75
|
+
"type": "string"
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"type": "object",
|
|
79
|
+
"properties": {
|
|
80
|
+
"src": { "type": "string" },
|
|
81
|
+
"response-headers": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"patternProperties": {
|
|
84
|
+
"^[^\n]+$": {
|
|
85
|
+
"type":"object",
|
|
86
|
+
"patternProperties": { "^[^\n]+$": { "type":"string" } }
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"additionalProperties": false
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
"runtimeManifest": {
|
|
96
|
+
"type": "object",
|
|
97
|
+
"properties": {
|
|
98
|
+
"packages": { "$ref": "#/definitions/packages" }
|
|
99
|
+
},
|
|
100
|
+
"required": ["packages"]
|
|
101
|
+
},
|
|
102
|
+
"packages": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"patternProperties": {
|
|
105
|
+
"^[^\n]+$": {
|
|
106
|
+
"$ref": "#/definitions/package"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"additionalProperties": false
|
|
110
|
+
},
|
|
111
|
+
"package": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"properties": {
|
|
114
|
+
"license": { "type": "string" },
|
|
115
|
+
"actions": { "$ref": "#/definitions/actions" }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"actions": {
|
|
119
|
+
"type": "object",
|
|
120
|
+
"patternProperties": {
|
|
121
|
+
"^[^\n]+$": {
|
|
122
|
+
"$ref": "#/definitions/action"
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"additionalProperties": false
|
|
126
|
+
},
|
|
127
|
+
"action": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"properties": {
|
|
130
|
+
"function": { "type": "string" },
|
|
131
|
+
"web": { "type": "string" },
|
|
132
|
+
"runtime": { "type": "string" },
|
|
133
|
+
"inputs": { "$ref": "#/definitions/inputs" },
|
|
134
|
+
"annotations": { "$ref": "#/definitions/annotations" }
|
|
135
|
+
},
|
|
136
|
+
"required": []
|
|
137
|
+
},
|
|
138
|
+
"inputs": {
|
|
139
|
+
"type": "object",
|
|
140
|
+
"patternProperties": {
|
|
141
|
+
"^[^\n]+$": {
|
|
142
|
+
"type": ["string", "boolean"]
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"additionalProperties": false
|
|
146
|
+
},
|
|
147
|
+
"annotations": {
|
|
148
|
+
"type": "object",
|
|
149
|
+
"patternProperties": {
|
|
150
|
+
"^[^\n]+$": {
|
|
151
|
+
"type": ["string", "boolean"]
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
"additionalProperties": false
|
|
155
|
+
},
|
|
156
|
+
"hooks": {
|
|
157
|
+
"type": "object",
|
|
158
|
+
"properties": {
|
|
159
|
+
"pre-app-build": { "type": "string" },
|
|
160
|
+
"post-app-build": { "type": "string" },
|
|
161
|
+
"build-actions": { "type": "string" },
|
|
162
|
+
"build-static": { "type": "string" },
|
|
163
|
+
"pre-app-deploy": { "type": "string" },
|
|
164
|
+
"post-app-deploy": { "type": "string" },
|
|
165
|
+
"deploy-actions": { "type": "string" },
|
|
166
|
+
"deploy-static": { "type": "string" },
|
|
167
|
+
"pre-app-undeploy": { "type": "string" },
|
|
168
|
+
"post-app-undeploy": { "type": "string" },
|
|
169
|
+
"undeploy-actions": { "type": "string" },
|
|
170
|
+
"undeploy-static": { "type": "string" },
|
|
171
|
+
"pre-app-run": { "type": "string" },
|
|
172
|
+
"post-app-run": { "type": "string" },
|
|
173
|
+
"serve-static": { "type": "string" }
|
|
174
|
+
}
|
|
175
|
+
},
|
|
176
|
+
"events": {
|
|
177
|
+
"type": "object",
|
|
178
|
+
"properties": {
|
|
179
|
+
"registrations": {
|
|
180
|
+
"type": "object",
|
|
181
|
+
"patternProperties": {
|
|
182
|
+
"^[^\n]+$": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"properties": {
|
|
185
|
+
"description": { "type": "string" },
|
|
186
|
+
"events_of_interest": {
|
|
187
|
+
"type": "array",
|
|
188
|
+
"items": {
|
|
189
|
+
"type": "object",
|
|
190
|
+
"properties": {
|
|
191
|
+
"provider_metadata": { "type": "string" },
|
|
192
|
+
"event_codes": { "type": "array" }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
},
|
|
196
|
+
"runtime_action": {"type": "string" }
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
"configSchema": {
|
|
204
|
+
"type": "array",
|
|
205
|
+
"maxItems": 50,
|
|
206
|
+
"items": {
|
|
207
|
+
"type": "object",
|
|
208
|
+
"properties": {
|
|
209
|
+
"type": { "type": "string", "enum": ["string", "boolean"] },
|
|
210
|
+
"title": { "type": "string", "maxLength": 200 },
|
|
211
|
+
"envKey": { "type": "string", "pattern": "[a-zA-Z_]{1,}[a-zA-Z0-9_]{0,}", "maxLength": 100 },
|
|
212
|
+
"enum": { "type": "array", "items": { "$ref": "#/definitions/configSchemaValue" }, "minItems": 1, "maxItems": 100 },
|
|
213
|
+
"default": { "$ref": "#/definitions/configSchemaValue" },
|
|
214
|
+
"secret": { "type": "boolean" }
|
|
215
|
+
},
|
|
216
|
+
"required": ["type", "envKey"],
|
|
217
|
+
"additionalProperties": false
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
"configSchemaValue": { "type": "string", "maxLength": 1000 },
|
|
221
|
+
"productDependencies": {
|
|
222
|
+
"type": "array",
|
|
223
|
+
"items": {
|
|
224
|
+
"type": "object",
|
|
225
|
+
"properties": {
|
|
226
|
+
"code": { "type": "string" },
|
|
227
|
+
"minVersion": { "type": "string", "pattern": "^[0-9]+.[0-9]+.[0-9]+$" },
|
|
228
|
+
"maxVersion": { "type": "string", "pattern": "^[0-9]+.[0-9]+.[0-9]+$" }
|
|
229
|
+
},
|
|
230
|
+
"required": ["code", "minVersion", "maxVersion"],
|
|
231
|
+
"additionalProperties": false
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,11 @@ const yaml = require('js-yaml')
|
|
|
15
15
|
const fs = require('fs-extra')
|
|
16
16
|
const aioConfigLoader = require('@adobe/aio-lib-core-config')
|
|
17
17
|
const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-lib-app-config', { provider: 'debug' })
|
|
18
|
+
const Ajv = require('ajv')
|
|
19
|
+
const ajvAddFormats = require('ajv-formats')
|
|
20
|
+
|
|
21
|
+
// eslint-disable-next-line node/no-unpublished-require
|
|
22
|
+
const schema = require('../schema/app.config.yaml.schema.json')
|
|
18
23
|
|
|
19
24
|
// give or take daylight savings, and leap seconds ...
|
|
20
25
|
const AboutAWeekInSeconds = '604800'
|
|
@@ -34,6 +39,37 @@ const defaults = {
|
|
|
34
39
|
EXTENSIONS_CONFIG_KEY: 'extensions'
|
|
35
40
|
}
|
|
36
41
|
|
|
42
|
+
const HookKeys = [
|
|
43
|
+
'pre-app-build',
|
|
44
|
+
'post-app-build',
|
|
45
|
+
'build-actions',
|
|
46
|
+
'build-static',
|
|
47
|
+
'pre-app-deploy',
|
|
48
|
+
'post-app-deploy',
|
|
49
|
+
'deploy-actions',
|
|
50
|
+
'deploy-static',
|
|
51
|
+
'pre-app-undeploy',
|
|
52
|
+
'post-app-undeploy',
|
|
53
|
+
'undeploy-actions',
|
|
54
|
+
'undeploy-static',
|
|
55
|
+
'pre-app-run',
|
|
56
|
+
'post-app-run',
|
|
57
|
+
'serve-static'
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
// please add any key that points to a path here
|
|
61
|
+
// if they are defined in an included file, those need to be rewritten to be relative to the root folder
|
|
62
|
+
const PATH_KEYS = [
|
|
63
|
+
/^(application|extensions\.[^.]+)\.web$/,
|
|
64
|
+
/^(application|extensions\.[^.]+)\.web\.src$/,
|
|
65
|
+
/^(application|extensions\.[^.]+)\.actions$/,
|
|
66
|
+
/^(application|extensions\.[^.]+)\.unitTest$/,
|
|
67
|
+
/^(application|extensions\.[^.]+)\.e2eTest$/,
|
|
68
|
+
/^(application|extensions\.[^.]+)\.dist$/,
|
|
69
|
+
/^(application|extensions\.[^.]+)\.runtimeManifest\.packages\.[^.]+\.actions\.[^.]+\.function$/,
|
|
70
|
+
/^(application|extensions\.[^.]+)\.runtimeManifest\.packages\.[^.]+\.actions\.[^.]+\.include\.\d+\.0$/
|
|
71
|
+
]
|
|
72
|
+
|
|
37
73
|
const {
|
|
38
74
|
getCliEnv, /* function */
|
|
39
75
|
STAGE_ENV /* string */
|
|
@@ -41,10 +77,15 @@ const {
|
|
|
41
77
|
const cloneDeep = require('lodash.clonedeep')
|
|
42
78
|
|
|
43
79
|
/**
|
|
80
|
+
* Loads app builder configuration in the current working directory.
|
|
81
|
+
*
|
|
44
82
|
* loading config returns following object (this config is internal, not user facing):
|
|
45
83
|
* {
|
|
84
|
+
* configSchema: { app.config.yaml configSchema field }
|
|
46
85
|
* aio: {...aioConfig...},
|
|
47
86
|
* packagejson: {...package.json...},
|
|
87
|
+
* configSchema: {...app.config.yaml configSchema field as is},
|
|
88
|
+
* productDependencies: {...app.config.yaml productDependencies field as is},
|
|
48
89
|
* all: {
|
|
49
90
|
* OPTIONAL:'application': {
|
|
50
91
|
* app: {
|
|
@@ -83,7 +124,8 @@ const cloneDeep = require('lodash.clonedeep')
|
|
|
83
124
|
* dist,
|
|
84
125
|
* remote,
|
|
85
126
|
* urls
|
|
86
|
-
* }
|
|
127
|
+
* },
|
|
128
|
+
* events: {}
|
|
87
129
|
* }
|
|
88
130
|
* },
|
|
89
131
|
* OPTIONAL:'dx/asset-compute/worker/1': {
|
|
@@ -94,30 +136,51 @@ const cloneDeep = require('lodash.clonedeep')
|
|
|
94
136
|
* },
|
|
95
137
|
* }
|
|
96
138
|
*
|
|
97
|
-
* @param {object} options options to
|
|
139
|
+
* @param {object} options options to load Config
|
|
98
140
|
* @param {boolean} options.allowNoImpl do not throw if there is no implementation
|
|
141
|
+
* @param {boolean} options.ignoreAioConfig do not load .aio config via aio-lib-core-config, which is loaded synchronously and blocks the main thread.
|
|
99
142
|
* @returns {object} the config
|
|
100
143
|
*/
|
|
101
|
-
function
|
|
144
|
+
async function load (options = {}) {
|
|
145
|
+
const allowNoImpl = options.allowNoImpl === undefined ? false : options.allowNoImpl
|
|
146
|
+
const ignoreAioConfig = options.ignoreAioConfig === undefined ? false : options.ignoreAioConfig
|
|
147
|
+
// *NOTE* it would be nice to support an appFolder option to load config from a different folder.
|
|
148
|
+
// However, this requires to update aio-lib-core-config to support loading
|
|
149
|
+
// from a different folder aswell (or enforcing ignore).
|
|
150
|
+
|
|
151
|
+
// I. load common config
|
|
102
152
|
// configuration that is shared for application and each extension config
|
|
103
153
|
// holds things like ow credentials, packagejson and aioConfig
|
|
104
|
-
const commonConfig = loadCommonConfig()
|
|
105
|
-
checkCommonConfig(commonConfig)
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
154
|
+
const commonConfig = await loadCommonConfig({ ignoreAioConfig })
|
|
155
|
+
// checkCommonConfig(commonConfig)
|
|
156
|
+
|
|
157
|
+
// II. load app.config.yaml & validate + load/merge legacy configuration if any
|
|
158
|
+
// support backward compatibility, include legacy application configuration
|
|
159
|
+
const legacyAppConfigWithIndex = await legacyToAppConfig(commonConfig)
|
|
160
|
+
let { config: appConfig, includeIndex } = legacyAppConfigWithIndex
|
|
161
|
+
// no validation on the legacy configuration, which will be deprecated eventually
|
|
162
|
+
|
|
163
|
+
if (await fs.exists(defaults.USER_CONFIG_FILE)) {
|
|
164
|
+
// this will resolve $include directives and output the app config into a single object
|
|
165
|
+
// paths config values in $included files will be rewritten
|
|
166
|
+
const appConfigWithIndex = await coalesce(defaults.USER_CONFIG_FILE, { absolutePaths: true })
|
|
167
|
+
await validate(appConfigWithIndex.config, { throws: true })
|
|
168
|
+
const mergedAppConfig = await mergeLegacyAppConfig(appConfigWithIndex, legacyAppConfigWithIndex)
|
|
169
|
+
|
|
170
|
+
appConfig = mergedAppConfig.config
|
|
171
|
+
includeIndex = mergedAppConfig.includeIndex
|
|
172
|
+
}
|
|
114
173
|
|
|
174
|
+
// III. build output object
|
|
175
|
+
// full standalone application and extension configurations
|
|
176
|
+
const all = await buildAllConfigs(appConfig, commonConfig, includeIndex)
|
|
115
177
|
const impl = Object.keys(all).sort() // sort for predictable configuration
|
|
116
|
-
if (!
|
|
178
|
+
if (!allowNoImpl && impl.length <= 0) {
|
|
117
179
|
throw new Error(`Couldn't find configuration in '${process.cwd()}', make sure to add at least one extension or a standalone app`)
|
|
118
180
|
}
|
|
119
|
-
|
|
120
181
|
return {
|
|
182
|
+
configSchema: appConfig?.configSchema || [],
|
|
183
|
+
productDependencies: appConfig?.productDependencies || [],
|
|
121
184
|
all,
|
|
122
185
|
implements: impl, // e.g. 'dx/excshell/1', 'application'
|
|
123
186
|
// includeIndex keeps a map from config keys to files that includes them and the relative key in the file.
|
|
@@ -129,13 +192,43 @@ function loadConfig (options = { allowNoImpl: false }) {
|
|
|
129
192
|
}
|
|
130
193
|
}
|
|
131
194
|
|
|
195
|
+
/**
|
|
196
|
+
* Validates the app configuration.
|
|
197
|
+
* To validate an app.config.yaml file, use `await validate(await coalesce('app.config.yaml'))`
|
|
198
|
+
*
|
|
199
|
+
* @param {object} coalescedAppConfigObj the resolved app config object.
|
|
200
|
+
* @param {object} options options
|
|
201
|
+
* @param {boolean} options.throws defaults to false, if true throws on validation error instead of returning the error
|
|
202
|
+
* @throws if not valid
|
|
203
|
+
*/
|
|
204
|
+
async function validate (coalescedAppConfigObj, options = {}) {
|
|
205
|
+
const throws = options.throws === undefined ? false : options.throws
|
|
206
|
+
/* eslint-disable-next-line node/no-unpublished-require */
|
|
207
|
+
const ajv = new Ajv({
|
|
208
|
+
allErrors: true,
|
|
209
|
+
allowUnionTypes: true
|
|
210
|
+
})
|
|
211
|
+
ajvAddFormats(ajv)
|
|
212
|
+
const validate = ajv.compile(schema)
|
|
213
|
+
|
|
214
|
+
const valid = validate(coalescedAppConfigObj)
|
|
215
|
+
const errors = validate.errors
|
|
216
|
+
if (!valid && throws) {
|
|
217
|
+
throw new Error(`Missing or invalid keys in ${defaults.USER_CONFIG_FILE}: ${JSON.stringify(errors, null, 2)}`)
|
|
218
|
+
}
|
|
219
|
+
return { valid, errors }
|
|
220
|
+
}
|
|
221
|
+
|
|
132
222
|
/** @private */
|
|
133
|
-
function loadCommonConfig () {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
223
|
+
async function loadCommonConfig (/* istanbul ignore next */options = {}) {
|
|
224
|
+
let aioConfig = {}
|
|
225
|
+
if (!options.ignoreAioConfig) {
|
|
226
|
+
// load aio config (mostly runtime and console config)
|
|
227
|
+
aioConfigLoader.reload()
|
|
228
|
+
aioConfig = aioConfigLoader.get() || {}
|
|
229
|
+
}
|
|
137
230
|
|
|
138
|
-
const packagejson = fs.
|
|
231
|
+
const packagejson = await fs.readJson('package.json', { throws: true })
|
|
139
232
|
|
|
140
233
|
// defaults
|
|
141
234
|
// remove scoped name to use this for open whisk entities
|
|
@@ -150,7 +243,7 @@ function loadCommonConfig () {
|
|
|
150
243
|
owConfig.defaultApihost = defaults.defaultOwApihost
|
|
151
244
|
owConfig.apihost = owConfig.apihost || defaults.defaultOwApihost // set by user
|
|
152
245
|
owConfig.apiversion = owConfig.apiversion || 'v1'
|
|
153
|
-
// default package name replacing __APP_PACKAGE__ placeholder
|
|
246
|
+
// default package name for replacing the legacy __APP_PACKAGE__ placeholder
|
|
154
247
|
owConfig.package = `${packagejson.name}-${packagejson.version}`
|
|
155
248
|
|
|
156
249
|
return {
|
|
@@ -158,62 +251,51 @@ function loadCommonConfig () {
|
|
|
158
251
|
ow: owConfig,
|
|
159
252
|
aio: aioConfig,
|
|
160
253
|
// soon not needed anymore (for old headless validator)
|
|
161
|
-
imsOrgId: aioConfig
|
|
254
|
+
imsOrgId: aioConfig.project?.org?.ims_org_id
|
|
162
255
|
}
|
|
163
256
|
}
|
|
164
257
|
|
|
165
258
|
/** @private */
|
|
166
|
-
function checkCommonConfig (commonConfig) {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/** @private */
|
|
174
|
-
function loadUserConfig (commonConfig) {
|
|
175
|
-
const { config: legacyConfig, includeIndex: legacyIncludeIndex } = loadUserConfigLegacy(commonConfig)
|
|
176
|
-
const { config, includeIndex } = loadUserConfigAppYaml()
|
|
177
|
-
|
|
178
|
-
const ret = {}
|
|
179
|
-
// include legacy application configuration
|
|
180
|
-
ret.config = mergeLegacyUserConfig(config, legacyConfig)
|
|
181
|
-
// merge includeIndexes, new config index takes precedence
|
|
182
|
-
ret.includeIndex = { ...legacyIncludeIndex, ...includeIndex }
|
|
183
|
-
|
|
184
|
-
return ret
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
/** @private */
|
|
188
|
-
function loadUserConfigAppYaml () {
|
|
189
|
-
if (!fs.existsSync(defaults.USER_CONFIG_FILE)) {
|
|
190
|
-
// no error, support for legacy configuration
|
|
191
|
-
return { config: {}, includeIndex: {} }
|
|
192
|
-
}
|
|
259
|
+
// function checkCommonConfig (commonConfig) {
|
|
260
|
+
// // todo this depends on the commands, expose a throwOnMissingConsoleInfo ?
|
|
261
|
+
// // if (!commonConfig.aio.project || !commonConfig.ow.auth) {
|
|
262
|
+
// // throw new Error('Missing project configuration, import a valid Console configuration first via \'aio app use\'')
|
|
263
|
+
// // }
|
|
264
|
+
// }
|
|
193
265
|
|
|
266
|
+
/**
|
|
267
|
+
* Resolve all includes, update relative paths and return a coalesced app configuration object.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} appConfigFile path to the app.config.yaml
|
|
270
|
+
* @param {object} options options
|
|
271
|
+
* @param {object} options.absolutePaths boolean, true for absolute paths, default for relative to appConfigFile directory.
|
|
272
|
+
* @returns {object} single appConfig with resolved includes
|
|
273
|
+
*/
|
|
274
|
+
async function coalesce (appConfigFile, options = {}) {
|
|
194
275
|
// this code is traversing app.config.yaml recursively to resolve all $includes directives
|
|
195
276
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const config = yaml.safeLoad(fs.readFileSync(defaults.USER_CONFIG_FILE, 'utf8'))
|
|
277
|
+
const absolutePaths = options.absolutePaths === undefined ? false : options.absolutePaths
|
|
278
|
+
const config = yaml.safeLoad(await fs.readFile(appConfigFile, 'utf8'))
|
|
199
279
|
// keep an index that will map keys like 'extensions.abc.runtimeManifest' to the config file where there are defined
|
|
200
280
|
const includeIndex = {}
|
|
201
281
|
// keep a cache for common included files - avoid to read a same file twice
|
|
202
282
|
const configCache = {}
|
|
203
|
-
|
|
283
|
+
|
|
284
|
+
// stack entries to be iterated on
|
|
204
285
|
/** @private */
|
|
205
286
|
function buildStackEntries (obj, fullKeyParent, relativeFullKeyParent, includedFiles, filterKeys = null) {
|
|
206
287
|
return Object.keys(obj || {})
|
|
207
288
|
// include filtered keys only
|
|
208
289
|
.filter(key => !filterKeys || filterKeys.includes(key))
|
|
209
|
-
// parentObj
|
|
210
|
-
// includedFiles
|
|
211
|
-
//
|
|
212
|
-
//
|
|
290
|
+
// `parentObj` stores the parent config object
|
|
291
|
+
// `includedFiles` tracks already included files, is used for cycle detection and building the index key,
|
|
292
|
+
// `fullKey` store parents and used for building the index,
|
|
293
|
+
// `relativeFullKey` stores the key relative to the included file (e.g. actions are included actions.
|
|
213
294
|
.map(key => ({ parentObj: obj, includedFiles, key, fullKey: fullKeyParent.concat(`.${key}`), relativeFullKey: relativeFullKeyParent.concat(`.${key}`) }))
|
|
214
295
|
}
|
|
215
|
-
|
|
216
|
-
|
|
296
|
+
|
|
297
|
+
// initialize with top level config object, each key will be traversed and checked for $include directive
|
|
298
|
+
const traverseStack = buildStackEntries(config, '', '', [appConfigFile])
|
|
217
299
|
|
|
218
300
|
// ITERATIONS
|
|
219
301
|
// iterate until there are no entries
|
|
@@ -223,7 +305,8 @@ function loadUserConfigAppYaml () {
|
|
|
223
305
|
const currConfigFile = includedFiles[includedFiles.length - 1]
|
|
224
306
|
|
|
225
307
|
// add full key to the index, slice(1) to remove initial dot
|
|
226
|
-
|
|
308
|
+
const fullIndexKey = fullKey.slice(1)
|
|
309
|
+
includeIndex[fullIndexKey] = {
|
|
227
310
|
file: currConfigFile,
|
|
228
311
|
key: relativeFullKey.slice(1)
|
|
229
312
|
}
|
|
@@ -231,7 +314,7 @@ function loadUserConfigAppYaml () {
|
|
|
231
314
|
const value = parentObj[key]
|
|
232
315
|
|
|
233
316
|
if (typeof value === 'object') {
|
|
234
|
-
// if value is an object or an array, add entries
|
|
317
|
+
// if value is an object or an array, add new entries to be traversed
|
|
235
318
|
traverseStack.push(...buildStackEntries(value, fullKey, relativeFullKey, includedFiles))
|
|
236
319
|
continue
|
|
237
320
|
}
|
|
@@ -239,24 +322,24 @@ function loadUserConfigAppYaml () {
|
|
|
239
322
|
if (key === defaults.INCLUDE_DIRECTIVE) {
|
|
240
323
|
// $include: 'configFile', value is string pointing to config file
|
|
241
324
|
// includes are relative to the current config file
|
|
325
|
+
|
|
242
326
|
// config path in index always as unix path, it doesn't matter but makes it easier to generate testing mock data
|
|
243
327
|
const incFile = path.join(path.dirname(currConfigFile), value)
|
|
244
328
|
const configFile = incFile.split(path.sep).join(path.posix.sep)
|
|
245
|
-
// const configFile = upath.toUnix(path.join(path.dirname(currConfigFile), value))
|
|
246
329
|
|
|
247
330
|
// 1. check for include cycles
|
|
248
331
|
if (includedFiles.includes(configFile)) {
|
|
249
332
|
throw new Error(`Detected '${defaults.INCLUDE_DIRECTIVE}' cycle: '${[...includedFiles, configFile].toString()}', please make sure that your configuration has no cycles.`)
|
|
250
333
|
}
|
|
251
334
|
// 2. check if file exists
|
|
252
|
-
if (!configCache[configFile] && !fs.
|
|
335
|
+
if (!configCache[configFile] && !(await fs.exists(configFile))) {
|
|
253
336
|
throw new Error(`'${defaults.INCLUDE_DIRECTIVE}: ${configFile}' cannot be resolved, please make sure the file exists.`)
|
|
254
337
|
}
|
|
255
338
|
// 3. delete the $include directive to be replaced
|
|
256
339
|
delete parentObj[key]
|
|
257
340
|
// 4. load the included file
|
|
258
|
-
// Note the included file can in turn also have includes
|
|
259
|
-
const loadedConfig = configCache[configFile] || yaml.safeLoad(fs.
|
|
341
|
+
// Note the included file can in turn also have includes, so we will have to traverse it as well
|
|
342
|
+
const loadedConfig = configCache[configFile] || yaml.safeLoad(await fs.readFile(configFile, 'utf8'))
|
|
260
343
|
if (Array.isArray(loadedConfig) || typeof loadedConfig !== 'object') {
|
|
261
344
|
throw new Error(`'${defaults.INCLUDE_DIRECTIVE}: ${configFile}' does not resolve to an object. Including an array or primitive type config is not supported.`)
|
|
262
345
|
}
|
|
@@ -274,13 +357,14 @@ function loadUserConfigAppYaml () {
|
|
|
274
357
|
// else primitive types: do nothing
|
|
275
358
|
}
|
|
276
359
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
360
|
+
const appConfigWithIncludeIndex = { config, includeIndex }
|
|
361
|
+
rewritePathsInPlace(appConfigWithIncludeIndex, { absolutePaths })
|
|
362
|
+
|
|
363
|
+
return appConfigWithIncludeIndex
|
|
280
364
|
}
|
|
281
365
|
|
|
282
366
|
/** @private */
|
|
283
|
-
function
|
|
367
|
+
async function legacyToAppConfig (commonConfig) {
|
|
284
368
|
// load legacy user app config from manifest.yml, package.json, .aio.app
|
|
285
369
|
const includeIndex = {}
|
|
286
370
|
const legacyAppConfig = {}
|
|
@@ -289,17 +373,19 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
289
373
|
// todo: new value usingLegacyConfig
|
|
290
374
|
// this module should not console.log/warn ... or include chalk ...
|
|
291
375
|
if (commonConfig.aio.cna !== undefined || commonConfig.aio.app !== undefined) {
|
|
292
|
-
|
|
376
|
+
// this might have never have been seen in the wild as we don't know
|
|
377
|
+
// what log-level users have set
|
|
378
|
+
aioLogger.error('App config in \'.aio\' file is deprecated. Please move your \'.aio.app\' or \'.aio.cna\' to \'app.config.yaml\'.')
|
|
293
379
|
const appConfig = { ...commonConfig.aio.app, ...commonConfig.aio.cna }
|
|
294
|
-
Object.entries(appConfig).
|
|
380
|
+
Object.entries(appConfig).forEach(([k, v]) => {
|
|
295
381
|
legacyAppConfig[k] = v
|
|
296
382
|
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}.${k}`] = { file: '.aio', key: `app.${k}` }
|
|
297
383
|
})
|
|
298
384
|
}
|
|
299
385
|
|
|
300
386
|
// 2. load legacy manifest.yaml
|
|
301
|
-
if (fs.
|
|
302
|
-
const runtimeManifest = yaml.safeLoad(fs.
|
|
387
|
+
if (await fs.exists(defaults.LEGACY_RUNTIME_MANIFEST)) {
|
|
388
|
+
const runtimeManifest = yaml.safeLoad(await fs.readFile(defaults.LEGACY_RUNTIME_MANIFEST, 'utf8'))
|
|
303
389
|
legacyAppConfig.runtimeManifest = runtimeManifest
|
|
304
390
|
// populate index
|
|
305
391
|
const baseKey = `${defaults.APPLICATION_CONFIG_KEY}.runtimeManifest`
|
|
@@ -321,30 +407,17 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
321
407
|
if (pkgjsonscripts) {
|
|
322
408
|
const hooks = {}
|
|
323
409
|
// https://www.adobe.io/apis/experienceplatform/project-firefly/docs.html#!AdobeDocs/project-firefly/master/guides/app-hooks.md
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
hooks['post-app-deploy'] = pkgjsonscripts['post-app-deploy']
|
|
330
|
-
hooks['deploy-actions'] = pkgjsonscripts['deploy-actions']
|
|
331
|
-
hooks['deploy-static'] = pkgjsonscripts['deploy-static']
|
|
332
|
-
hooks['pre-app-undeploy'] = pkgjsonscripts['pre-app-undeploy']
|
|
333
|
-
hooks['post-app-undeploy'] = pkgjsonscripts['post-app-undeploy']
|
|
334
|
-
hooks['undeploy-actions'] = pkgjsonscripts['undeploy-actions']
|
|
335
|
-
hooks['undeploy-static'] = pkgjsonscripts['undeploy-static']
|
|
336
|
-
hooks['pre-app-run'] = pkgjsonscripts['pre-app-run']
|
|
337
|
-
hooks['post-app-run'] = pkgjsonscripts['post-app-run']
|
|
338
|
-
hooks['serve-static'] = pkgjsonscripts['serve-static']
|
|
339
|
-
// remove undefined hooks
|
|
340
|
-
Object.entries(hooks).forEach(([k, v]) => {
|
|
341
|
-
if (!hooks[k]) {
|
|
342
|
-
delete hooks[k]
|
|
410
|
+
|
|
411
|
+
HookKeys.forEach(hookKey => {
|
|
412
|
+
if (pkgjsonscripts[hookKey]) {
|
|
413
|
+
hooks[hookKey] = pkgjsonscripts[hookKey]
|
|
414
|
+
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}.hooks.${hookKey}`] = { file: 'package.json', key: `scripts.${hookKey}` }
|
|
343
415
|
}
|
|
344
416
|
})
|
|
417
|
+
|
|
345
418
|
// todo: new val usingLegacyHooks:Boolean
|
|
346
419
|
if (Object.keys(hooks).length > 0) {
|
|
347
|
-
aioLogger.
|
|
420
|
+
aioLogger.error('hooks in \'package.json\' are deprecated. Please move your hooks to \'app.config.yaml\' under the \'hooks\' key')
|
|
348
421
|
legacyAppConfig.hooks = hooks
|
|
349
422
|
// build index
|
|
350
423
|
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}.hooks`] = { file: 'package.json', key: 'scripts' }
|
|
@@ -358,80 +431,126 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
358
431
|
}
|
|
359
432
|
}
|
|
360
433
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
434
|
+
const appConfigWithIncludeIndex = { includeIndex, config: { [defaults.APPLICATION_CONFIG_KEY]: legacyAppConfig } }
|
|
435
|
+
if (Object.keys(includeIndex).length <= 0) {
|
|
436
|
+
// no legacy configuration return now
|
|
437
|
+
// todo return undefined here and for normal config, rewrite load and merge logic
|
|
438
|
+
return appConfigWithIncludeIndex
|
|
364
439
|
}
|
|
365
440
|
|
|
366
|
-
|
|
441
|
+
// add the top key
|
|
442
|
+
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}`] = { file: '.aio', key: 'app' }
|
|
443
|
+
|
|
444
|
+
/* always absolute paths for now. Note that if we were interested in relative
|
|
445
|
+
paths there would be no need to rewrite, as all paths are
|
|
446
|
+
defined in the app root folder for legacy apps */
|
|
447
|
+
rewritePathsInPlace(appConfigWithIncludeIndex, { absolutePaths: true })
|
|
448
|
+
|
|
449
|
+
return appConfigWithIncludeIndex
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** @private */
|
|
453
|
+
function rewritePathsInPlace (appConfigWithIncludeIndex, options) {
|
|
454
|
+
const { config: appConfig, includeIndex } = appConfigWithIncludeIndex
|
|
455
|
+
|
|
456
|
+
const buildStackEntries = (currObj, currFullKey) => Object.keys(currObj || {} /* cover for null */).map(k => {
|
|
457
|
+
const fullKey = currFullKey ? currFullKey + '.' + k : k
|
|
458
|
+
const includedFromConfigFile = includeIndex[fullKey].file
|
|
459
|
+
return { fullKey, includedFromConfigFile, key: k, parentObj: currObj }
|
|
460
|
+
})
|
|
461
|
+
const stack = buildStackEntries(appConfig)
|
|
462
|
+
|
|
463
|
+
while (stack.length > 0) {
|
|
464
|
+
const { fullKey, includedFromConfigFile, key, parentObj } = stack.pop()
|
|
465
|
+
const value = parentObj[key]
|
|
466
|
+
|
|
467
|
+
if (typeof value === 'string' && PATH_KEYS.filter(reg => fullKey.match(reg)).length) {
|
|
468
|
+
// rewrite path value to be relative to the root instead of being relative to the config file that includes it
|
|
469
|
+
parentObj[key] = resolveToRoot(value, includedFromConfigFile, { absolutePaths: options.absolutePaths })
|
|
470
|
+
}
|
|
471
|
+
if (typeof value === 'object') {
|
|
472
|
+
// object or Array
|
|
473
|
+
stack.push(...buildStackEntries(value, fullKey))
|
|
474
|
+
}
|
|
475
|
+
}
|
|
367
476
|
}
|
|
368
477
|
|
|
369
478
|
/** @private */
|
|
370
|
-
function
|
|
371
|
-
// NOTE: here we do a simplified merge, deep merge with copy might be wanted
|
|
479
|
+
async function mergeLegacyAppConfig (appConfigWithIncludeIndex, legacyAppConfigWithIncludeIndex) {
|
|
480
|
+
// NOTE: here we do a simplified merge, deep merge with copy might be wanted
|
|
372
481
|
|
|
373
|
-
// only need to merge application configs as legacy config system
|
|
374
|
-
const
|
|
375
|
-
const
|
|
482
|
+
// only need to merge application configs as legacy config system does not work with extensions
|
|
483
|
+
const application = appConfigWithIncludeIndex.config[defaults.APPLICATION_CONFIG_KEY]
|
|
484
|
+
const legacyApplication = legacyAppConfigWithIncludeIndex.config[defaults.APPLICATION_CONFIG_KEY]
|
|
376
485
|
|
|
377
486
|
// merge 1 level config fields, such as 'actions': 'path/to/actions', precedence for new config
|
|
378
|
-
const
|
|
487
|
+
const mergedApplication = { ...legacyApplication, ...application }
|
|
379
488
|
|
|
380
489
|
// special cases if both are defined
|
|
381
|
-
if (
|
|
490
|
+
if (application && legacyApplication) {
|
|
382
491
|
// for simplicity runtimeManifest is not merged, it's one or the other
|
|
383
|
-
if (
|
|
492
|
+
if (legacyApplication.runtimeManifest && application.runtimeManifest) {
|
|
384
493
|
aioLogger.warn('\'manifest.yml\' is ignored in favor of key \'runtimeManifest\' in \'app.config.yaml\'.')
|
|
385
494
|
}
|
|
386
495
|
// hooks are merged
|
|
387
|
-
if (
|
|
388
|
-
|
|
496
|
+
if (legacyApplication.hooks && application.hooks) {
|
|
497
|
+
mergedApplication.hooks = { ...legacyApplication.hooks, ...application.hooks }
|
|
389
498
|
}
|
|
390
499
|
}
|
|
391
500
|
|
|
392
501
|
return {
|
|
393
|
-
|
|
394
|
-
|
|
502
|
+
config: {
|
|
503
|
+
...appConfigWithIncludeIndex.config,
|
|
504
|
+
[defaults.APPLICATION_CONFIG_KEY]: mergedApplication
|
|
505
|
+
},
|
|
506
|
+
// new configuration index takes precedence
|
|
507
|
+
includeIndex: { ...legacyAppConfigWithIncludeIndex.includeIndex, ...appConfigWithIncludeIndex.includeIndex }
|
|
395
508
|
}
|
|
396
509
|
}
|
|
397
510
|
|
|
398
511
|
/** @private */
|
|
399
|
-
function buildAllConfigs (userConfig, commonConfig, includeIndex) {
|
|
512
|
+
async function buildAllConfigs (userConfig, commonConfig, includeIndex) {
|
|
400
513
|
return {
|
|
401
|
-
...buildAppConfig(userConfig, commonConfig, includeIndex),
|
|
402
|
-
...buildExtConfigs(userConfig, commonConfig, includeIndex)
|
|
514
|
+
...(await buildAppConfig(userConfig, commonConfig, includeIndex)),
|
|
515
|
+
...(await buildExtConfigs(userConfig, commonConfig, includeIndex))
|
|
403
516
|
}
|
|
404
517
|
}
|
|
405
518
|
|
|
406
519
|
/** @private */
|
|
407
|
-
function buildExtConfigs (userConfig, commonConfig, includeIndex) {
|
|
520
|
+
async function buildExtConfigs (userConfig, commonConfig, includeIndex) {
|
|
408
521
|
const configs = {}
|
|
409
522
|
if (userConfig[defaults.EXTENSIONS_CONFIG_KEY]) {
|
|
410
|
-
Object.entries(userConfig[defaults.EXTENSIONS_CONFIG_KEY])
|
|
411
|
-
|
|
523
|
+
const entries = Object.entries(userConfig[defaults.EXTENSIONS_CONFIG_KEY])
|
|
524
|
+
for (const [extName, singleUserConfig] of entries) {
|
|
525
|
+
configs[extName] = await buildSingleConfig(extName, singleUserConfig, commonConfig, includeIndex)
|
|
412
526
|
// extensions have an extra operations field
|
|
413
527
|
configs[extName].operations = singleUserConfig.operations
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
528
|
+
// this is checked by the schema validation
|
|
529
|
+
// if (!configs[extName].operations) {
|
|
530
|
+
// throw new Error(`Missing 'operations' config field for extension point ${extName}`)
|
|
531
|
+
// }
|
|
532
|
+
}
|
|
418
533
|
}
|
|
419
534
|
return configs
|
|
420
535
|
}
|
|
421
536
|
|
|
422
537
|
/** @private */
|
|
423
|
-
function buildAppConfig (userConfig, commonConfig, includeIndex) {
|
|
424
|
-
const fullAppConfig = buildSingleConfig(defaults.APPLICATION_CONFIG_KEY,
|
|
538
|
+
async function buildAppConfig (userConfig, commonConfig, includeIndex) {
|
|
539
|
+
const fullAppConfig = await buildSingleConfig(defaults.APPLICATION_CONFIG_KEY,
|
|
540
|
+
userConfig[defaults.APPLICATION_CONFIG_KEY],
|
|
541
|
+
commonConfig,
|
|
542
|
+
includeIndex)
|
|
425
543
|
|
|
544
|
+
// todo: this needs to be updated; an app doesn't exist if there is no config.
|
|
426
545
|
if (!fullAppConfig.app.hasBackend && !fullAppConfig.app.hasFrontend) {
|
|
427
|
-
// only set application config if there is an
|
|
546
|
+
// only set application config if there is an actual app, meaning either some backend or frontend
|
|
428
547
|
return {}
|
|
429
548
|
}
|
|
430
549
|
return { [defaults.APPLICATION_CONFIG_KEY]: fullAppConfig }
|
|
431
550
|
}
|
|
432
551
|
|
|
433
552
|
/** @private */
|
|
434
|
-
function buildSingleConfig (configName, singleUserConfig, commonConfig, includeIndex) {
|
|
553
|
+
async function buildSingleConfig (configName, singleUserConfig, commonConfig, includeIndex) {
|
|
435
554
|
// used as subfolder folder in dist, converts to a single dir, e.g. dx/excshell/1 =>
|
|
436
555
|
// dx-excshell-1 and dist/dx-excshell-1/actions/action-xyz.zip
|
|
437
556
|
const subFolderName = configName.replace(/\//g, '-')
|
|
@@ -455,22 +574,33 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
455
574
|
return config
|
|
456
575
|
}
|
|
457
576
|
|
|
458
|
-
|
|
459
|
-
// The default action and web path are relative to the folder holding the config file.
|
|
577
|
+
// Default paths are relative to the folder holding the config file.
|
|
460
578
|
// Let's search the config path that defines a key in the same config object level as 'web' or
|
|
461
579
|
// 'action'
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
const
|
|
580
|
+
const otherKeyInObject = Object.keys(singleUserConfig)[0]
|
|
581
|
+
const configFilePath = includeIndex[`${fullKeyPrefix}.${otherKeyInObject}`].file
|
|
582
|
+
|
|
583
|
+
const defaultActionPath = resolveToRoot('actions/', configFilePath)
|
|
584
|
+
const defaultWebPath = resolveToRoot('web-src/', configFilePath)
|
|
585
|
+
const defaultUnitTestPath = resolveToRoot('test/', configFilePath)
|
|
586
|
+
const defaultE2eTestPath = resolveToRoot('e2e/', configFilePath)
|
|
466
587
|
const defaultDistPath = 'dist/' // relative to root
|
|
467
588
|
|
|
468
589
|
// absolute paths
|
|
469
|
-
const actions =
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
const
|
|
473
|
-
|
|
590
|
+
const actions = singleUserConfig.actions || defaultActionPath
|
|
591
|
+
const unitTest = singleUserConfig.unitTest || defaultUnitTestPath
|
|
592
|
+
const e2eTest = singleUserConfig.e2eTest || defaultE2eTestPath
|
|
593
|
+
const dist = singleUserConfig.dist || defaultDistPath
|
|
594
|
+
|
|
595
|
+
// web src folder might be defined in 'web' key or 'web.src' key
|
|
596
|
+
let web
|
|
597
|
+
if (typeof singleUserConfig.web === 'string') {
|
|
598
|
+
web = singleUserConfig.web
|
|
599
|
+
} else if (typeof singleUserConfig.web === 'object') {
|
|
600
|
+
web = singleUserConfig.web.src || defaultWebPath
|
|
601
|
+
} else {
|
|
602
|
+
web = defaultWebPath
|
|
603
|
+
}
|
|
474
604
|
|
|
475
605
|
config.tests.unit = path.resolve(unitTest)
|
|
476
606
|
config.tests.e2e = path.resolve(e2eTest)
|
|
@@ -478,15 +608,22 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
478
608
|
const manifest = singleUserConfig.runtimeManifest
|
|
479
609
|
|
|
480
610
|
config.app.hasBackend = !!manifest
|
|
481
|
-
config.app.hasFrontend = fs.
|
|
611
|
+
config.app.hasFrontend = await fs.exists(web)
|
|
482
612
|
config.app.dist = path.resolve(dist, dist === defaultDistPath ? subFolderName : '')
|
|
483
613
|
|
|
614
|
+
if (singleUserConfig.events) {
|
|
615
|
+
config.events = { ...singleUserConfig.events }
|
|
616
|
+
}
|
|
617
|
+
if (commonConfig?.aio?.project) {
|
|
618
|
+
config.project = commonConfig.aio.project
|
|
619
|
+
}
|
|
620
|
+
|
|
484
621
|
// actions
|
|
485
622
|
config.actions.src = path.resolve(actions) // needed for app add first action
|
|
486
623
|
if (config.app.hasBackend) {
|
|
487
624
|
config.actions.dist = path.join(config.app.dist, 'actions')
|
|
488
|
-
config.manifest = { src: 'manifest.yml' } // even
|
|
489
|
-
config.manifest.full =
|
|
625
|
+
config.manifest = { src: 'manifest.yml' } // even for non legacy config paths, it is required for runtime sync
|
|
626
|
+
config.manifest.full = cloneDeep(manifest)
|
|
490
627
|
config.manifest.packagePlaceholder = '__APP_PACKAGE__'
|
|
491
628
|
config.manifest.package = config.manifest.full.packages && config.manifest.full.packages[config.manifest.packagePlaceholder]
|
|
492
629
|
if (config.manifest.package) {
|
|
@@ -497,6 +634,9 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
497
634
|
|
|
498
635
|
// web
|
|
499
636
|
config.web.src = path.resolve(web) // needed for app add first web-assets
|
|
637
|
+
if (singleUserConfig.web && singleUserConfig.web['response-headers']) {
|
|
638
|
+
config.web['response-headers'] = singleUserConfig.web['response-headers']
|
|
639
|
+
}
|
|
500
640
|
if (config.app.hasFrontend) {
|
|
501
641
|
config.web.injectedConfig = path.resolve(path.join(web, 'src', 'config.json'))
|
|
502
642
|
// only add subfolder name if dist is default value
|
|
@@ -538,43 +678,21 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
538
678
|
return config
|
|
539
679
|
}
|
|
540
680
|
|
|
541
|
-
/** @private */
|
|
542
|
-
function rewriteRuntimeManifestPathsToRelRoot (manifestConfig, fullKeyToManifest, includeIndex) {
|
|
543
|
-
const manifestCopy = cloneDeep(manifestConfig)
|
|
544
|
-
|
|
545
|
-
Object.entries(manifestCopy.packages || {}).forEach(([pkgName, pkg]) => {
|
|
546
|
-
Object.entries(pkg.actions || {}).forEach(([actionName, action]) => {
|
|
547
|
-
const fullKeyToAction = `${fullKeyToManifest}.packages.${pkgName}.actions.${actionName}`
|
|
548
|
-
if (action.function) {
|
|
549
|
-
// absolut path
|
|
550
|
-
action.function = pathConfigValueToAbs(action.function, fullKeyToAction + '.function', includeIndex)
|
|
551
|
-
}
|
|
552
|
-
if (action.include) {
|
|
553
|
-
action.include.forEach((arr, i) => {
|
|
554
|
-
// absolut path
|
|
555
|
-
action.include[i][0] = pathConfigValueToAbs(action.include[i][0], fullKeyToAction + `.include.${i}.0`, includeIndex)
|
|
556
|
-
})
|
|
557
|
-
}
|
|
558
|
-
})
|
|
559
|
-
})
|
|
560
|
-
|
|
561
|
-
return manifestCopy
|
|
562
|
-
}
|
|
563
|
-
|
|
564
681
|
// Because of the $include directives, config paths (e.g actions: './path/to/actions') can
|
|
565
682
|
// be relative to config files in any subfolder. Config keys that define path values are
|
|
566
683
|
// identified and their value is rewritten relative to the root folder.
|
|
567
684
|
/** @private */
|
|
568
|
-
function
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
// path.resolve => support both absolut pathValue and relative (relative joins with
|
|
576
|
-
// config dir and process.cwd, absolut returns pathValue)
|
|
577
|
-
return path.resolve(path.dirname(configPath), pathValue)
|
|
685
|
+
function resolveToRoot (pathValue, includedFromConfigPath, options = {}) {
|
|
686
|
+
// path.resolve => support both absolute pathValue and relative (relative joins with
|
|
687
|
+
// config dir and process.cwd, absolute returns pathValue)
|
|
688
|
+
return options.absolutePaths
|
|
689
|
+
? path.resolve(path.dirname(includedFromConfigPath), pathValue)
|
|
690
|
+
// if relative keep unix paths
|
|
691
|
+
: path.join(path.dirname(includedFromConfigPath), pathValue).split(path.sep).join(path.posix.sep)
|
|
578
692
|
}
|
|
579
693
|
|
|
580
|
-
module.exports =
|
|
694
|
+
module.exports = {
|
|
695
|
+
load,
|
|
696
|
+
validate,
|
|
697
|
+
coalesce
|
|
698
|
+
}
|