@adobe/aio-cli-lib-app-config 1.0.1 → 2.0.1
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 +6 -3
- package/schema/app.config.yaml.schema.json +236 -0
- package/src/index.js +297 -172
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adobe/aio-cli-lib-app-config",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
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",
|
|
@@ -20,6 +21,8 @@
|
|
|
20
21
|
"@adobe/aio-lib-core-config": "^3.0.0",
|
|
21
22
|
"@adobe/aio-lib-core-logging": "^2.0.0",
|
|
22
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"
|
|
@@ -33,9 +36,9 @@
|
|
|
33
36
|
"eslint-plugin-import": "^2.25.3",
|
|
34
37
|
"eslint-plugin-jest": "^23",
|
|
35
38
|
"eslint-plugin-jsdoc": "^37",
|
|
39
|
+
"eslint-plugin-n": "^15",
|
|
36
40
|
"eslint-plugin-node": "^11.1.0",
|
|
37
41
|
"eslint-plugin-promise": "^6",
|
|
38
|
-
"eslint-plugin-n": "^15",
|
|
39
42
|
"eslint-plugin-standard": "^4.0.0",
|
|
40
43
|
"jest": "^27",
|
|
41
44
|
"jest-junit": "^10.0.0",
|
|
@@ -0,0 +1,236 @@
|
|
|
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
|
+
"type": "object",
|
|
26
|
+
"properties": {
|
|
27
|
+
"operations": {
|
|
28
|
+
"type": "object",
|
|
29
|
+
"patternProperties": {
|
|
30
|
+
"^[^\n]+$": {
|
|
31
|
+
"type":"array",
|
|
32
|
+
"items": {
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"type": { "type": "string" },
|
|
36
|
+
"impl": { "type": "string" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"minItems": 1
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"minProperties": 1
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"required": ["operations"]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"additionalProperties": false
|
|
49
|
+
},
|
|
50
|
+
"application": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"properties": {
|
|
53
|
+
"runtimeManifest": { "$ref": "#/definitions/runtimeManifest" },
|
|
54
|
+
"actions": { "type": "string" },
|
|
55
|
+
"unitTest": { "type": "string" },
|
|
56
|
+
"e2eTest": { "type": "string" },
|
|
57
|
+
"dist": { "type": "string" },
|
|
58
|
+
"tvmurl": { "type": "string" },
|
|
59
|
+
"awsaccesskeyid": { "type": "string" },
|
|
60
|
+
"awssecretaccesskey": { "type": "string" },
|
|
61
|
+
"s3bucket": { "type": "string" },
|
|
62
|
+
"events": { "$ref": "#/definitions/events" },
|
|
63
|
+
"hostname": { "type": "string" },
|
|
64
|
+
"htmlcacheduration": { "type": "number" },
|
|
65
|
+
"jscacheduration": { "type": "number" },
|
|
66
|
+
"csscacheduration": { "type": "number" },
|
|
67
|
+
"imagecacheduration": { "type": "number" },
|
|
68
|
+
"hooks": { "$ref": "#/definitions/hooks" },
|
|
69
|
+
"web": { "$ref": "#/definitions/web" }
|
|
70
|
+
},
|
|
71
|
+
"required": []
|
|
72
|
+
},
|
|
73
|
+
"web": {
|
|
74
|
+
"anyOf": [
|
|
75
|
+
{
|
|
76
|
+
"type": "string"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"type": "object",
|
|
80
|
+
"properties": {
|
|
81
|
+
"src": { "type": "string" },
|
|
82
|
+
"response-headers": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"patternProperties": {
|
|
85
|
+
"^[^\n]+$": {
|
|
86
|
+
"type":"object",
|
|
87
|
+
"patternProperties": { "^[^\n]+$": { "type":"string" } }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"additionalProperties": false
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"runtimeManifest": {
|
|
97
|
+
"type": "object",
|
|
98
|
+
"properties": {
|
|
99
|
+
"packages": { "$ref": "#/definitions/packages" }
|
|
100
|
+
},
|
|
101
|
+
"required": ["packages"]
|
|
102
|
+
},
|
|
103
|
+
"packages": {
|
|
104
|
+
"type": "object",
|
|
105
|
+
"patternProperties": {
|
|
106
|
+
"^[^\n]+$": {
|
|
107
|
+
"$ref": "#/definitions/package"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
"additionalProperties": false
|
|
111
|
+
},
|
|
112
|
+
"package": {
|
|
113
|
+
"type": "object",
|
|
114
|
+
"properties": {
|
|
115
|
+
"license": { "type": "string" },
|
|
116
|
+
"actions": { "$ref": "#/definitions/actions" }
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"actions": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"patternProperties": {
|
|
122
|
+
"^[^\n]+$": {
|
|
123
|
+
"$ref": "#/definitions/action"
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
"additionalProperties": false
|
|
127
|
+
},
|
|
128
|
+
"action": {
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {
|
|
131
|
+
"function": { "type": "string" },
|
|
132
|
+
"web": { "type": "string" },
|
|
133
|
+
"runtime": { "type": "string" },
|
|
134
|
+
"inputs": { "$ref": "#/definitions/inputs" },
|
|
135
|
+
"annotations": { "$ref": "#/definitions/annotations" }
|
|
136
|
+
},
|
|
137
|
+
"required": []
|
|
138
|
+
},
|
|
139
|
+
"inputs": {
|
|
140
|
+
"type": "object",
|
|
141
|
+
"patternProperties": {
|
|
142
|
+
"^[^\n]+$": {
|
|
143
|
+
"type": ["string", "boolean"]
|
|
144
|
+
}
|
|
145
|
+
},
|
|
146
|
+
"additionalProperties": false
|
|
147
|
+
},
|
|
148
|
+
"annotations": {
|
|
149
|
+
"type": "object",
|
|
150
|
+
"patternProperties": {
|
|
151
|
+
"^[^\n]+$": {
|
|
152
|
+
"type": ["string", "boolean"]
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
"additionalProperties": false
|
|
156
|
+
},
|
|
157
|
+
"hooks": {
|
|
158
|
+
"type": "object",
|
|
159
|
+
"properties": {
|
|
160
|
+
"pre-app-build": { "type": "string" },
|
|
161
|
+
"post-app-build": { "type": "string" },
|
|
162
|
+
"build-actions": { "type": "string" },
|
|
163
|
+
"build-static": { "type": "string" },
|
|
164
|
+
"pre-app-deploy": { "type": "string" },
|
|
165
|
+
"post-app-deploy": { "type": "string" },
|
|
166
|
+
"deploy-actions": { "type": "string" },
|
|
167
|
+
"deploy-static": { "type": "string" },
|
|
168
|
+
"pre-app-undeploy": { "type": "string" },
|
|
169
|
+
"post-app-undeploy": { "type": "string" },
|
|
170
|
+
"undeploy-actions": { "type": "string" },
|
|
171
|
+
"undeploy-static": { "type": "string" },
|
|
172
|
+
"pre-app-run": { "type": "string" },
|
|
173
|
+
"post-app-run": { "type": "string" },
|
|
174
|
+
"serve-static": { "type": "string" }
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"events": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"properties": {
|
|
180
|
+
"registrations": {
|
|
181
|
+
"type": "object",
|
|
182
|
+
"patternProperties": {
|
|
183
|
+
"^[^\n]+$": {
|
|
184
|
+
"type": "object",
|
|
185
|
+
"properties": {
|
|
186
|
+
"description": { "type": "string" },
|
|
187
|
+
"events_of_interest": {
|
|
188
|
+
"type": "array",
|
|
189
|
+
"items": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {
|
|
192
|
+
"provider_metadata": { "type": "string" },
|
|
193
|
+
"event_codes": { "type": "array" }
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
"runtime_action": {"type": "string" }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
"configSchema": {
|
|
205
|
+
"type": "array",
|
|
206
|
+
"maxItems": 50,
|
|
207
|
+
"items": {
|
|
208
|
+
"type": "object",
|
|
209
|
+
"properties": {
|
|
210
|
+
"type": { "type": "string", "enum": ["string", "boolean"] },
|
|
211
|
+
"title": { "type": "string", "maxLength": 200 },
|
|
212
|
+
"envKey": { "type": "string", "pattern": "[a-zA-Z_]{1,}[a-zA-Z0-9_]{0,}", "maxLength": 100 },
|
|
213
|
+
"enum": { "type": "array", "items": { "$ref": "#/definitions/configSchemaValue" }, "minItems": 1, "maxItems": 100 },
|
|
214
|
+
"default": { "$ref": "#/definitions/configSchemaValue" },
|
|
215
|
+
"secret": { "type": "boolean" }
|
|
216
|
+
},
|
|
217
|
+
"required": ["type", "envKey"],
|
|
218
|
+
"additionalProperties": false
|
|
219
|
+
}
|
|
220
|
+
},
|
|
221
|
+
"configSchemaValue": { "type": "string", "maxLength": 1000 },
|
|
222
|
+
"productDependencies": {
|
|
223
|
+
"type": "array",
|
|
224
|
+
"items": {
|
|
225
|
+
"type": "object",
|
|
226
|
+
"properties": {
|
|
227
|
+
"code": { "type": "string" },
|
|
228
|
+
"minVersion": { "type": "string", "pattern": "^[0-9]+.[0-9]+.[0-9]+$" },
|
|
229
|
+
"maxVersion": { "type": "string", "pattern": "^[0-9]+.[0-9]+.[0-9]+$" }
|
|
230
|
+
},
|
|
231
|
+
"required": ["code", "minVersion", "maxVersion"],
|
|
232
|
+
"additionalProperties": false
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
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,62 @@ 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
|
|
268
|
+
* configuration object.
|
|
269
|
+
*
|
|
270
|
+
* Returns the appConfig along with an index of config keys to config file. The
|
|
271
|
+
* config file paths in the index are absolute.
|
|
272
|
+
*
|
|
273
|
+
* @param {string} appConfigFile path to the app.config.yaml
|
|
274
|
+
* @param {object} options options
|
|
275
|
+
* @param {object} options.absolutePaths boolean, true for rewriting
|
|
276
|
+
* configuration paths to absolute, false for relative to the appConfigFile
|
|
277
|
+
* directory. Defaults to false. Note, that config values will never be
|
|
278
|
+
* rewritten as relative to the cwd. But also note that
|
|
279
|
+
* this option doesn't have any effect on the includeIndex paths which stay
|
|
280
|
+
* relative to the cwd.
|
|
281
|
+
* @returns {object} { config, includeIndex }
|
|
282
|
+
*/
|
|
283
|
+
async function coalesce (appConfigFile, options = {}) {
|
|
194
284
|
// this code is traversing app.config.yaml recursively to resolve all $includes directives
|
|
195
285
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
286
|
+
const absolutePaths = options.absolutePaths === undefined ? false : options.absolutePaths
|
|
287
|
+
const appRoot = path.dirname(appConfigFile)
|
|
288
|
+
|
|
289
|
+
const config = yaml.safeLoad(await fs.readFile(appConfigFile, 'utf8'))
|
|
199
290
|
// keep an index that will map keys like 'extensions.abc.runtimeManifest' to the config file where there are defined
|
|
200
291
|
const includeIndex = {}
|
|
201
292
|
// keep a cache for common included files - avoid to read a same file twice
|
|
202
293
|
const configCache = {}
|
|
203
|
-
|
|
294
|
+
|
|
295
|
+
// stack entries to be iterated on
|
|
204
296
|
/** @private */
|
|
205
297
|
function buildStackEntries (obj, fullKeyParent, relativeFullKeyParent, includedFiles, filterKeys = null) {
|
|
206
298
|
return Object.keys(obj || {})
|
|
207
299
|
// include filtered keys only
|
|
208
300
|
.filter(key => !filterKeys || filterKeys.includes(key))
|
|
209
|
-
// parentObj
|
|
210
|
-
// includedFiles
|
|
211
|
-
//
|
|
212
|
-
//
|
|
301
|
+
// `parentObj` stores the parent config object
|
|
302
|
+
// `includedFiles` tracks already included files, is used for cycle detection and building the index key,
|
|
303
|
+
// `fullKey` store parents and used for building the index,
|
|
304
|
+
// `relativeFullKey` stores the key relative to the included file (e.g. actions are included actions.
|
|
213
305
|
.map(key => ({ parentObj: obj, includedFiles, key, fullKey: fullKeyParent.concat(`.${key}`), relativeFullKey: relativeFullKeyParent.concat(`.${key}`) }))
|
|
214
306
|
}
|
|
215
|
-
|
|
216
|
-
|
|
307
|
+
|
|
308
|
+
// initialize with top level config object, each key will be traversed and checked for $include directive
|
|
309
|
+
const traverseStack = buildStackEntries(config, '', '', [appConfigFile])
|
|
217
310
|
|
|
218
311
|
// ITERATIONS
|
|
219
312
|
// iterate until there are no entries
|
|
@@ -223,7 +316,8 @@ function loadUserConfigAppYaml () {
|
|
|
223
316
|
const currConfigFile = includedFiles[includedFiles.length - 1]
|
|
224
317
|
|
|
225
318
|
// add full key to the index, slice(1) to remove initial dot
|
|
226
|
-
|
|
319
|
+
const fullIndexKey = fullKey.slice(1)
|
|
320
|
+
includeIndex[fullIndexKey] = {
|
|
227
321
|
file: currConfigFile,
|
|
228
322
|
key: relativeFullKey.slice(1)
|
|
229
323
|
}
|
|
@@ -231,7 +325,7 @@ function loadUserConfigAppYaml () {
|
|
|
231
325
|
const value = parentObj[key]
|
|
232
326
|
|
|
233
327
|
if (typeof value === 'object') {
|
|
234
|
-
// if value is an object or an array, add entries
|
|
328
|
+
// if value is an object or an array, add new entries to be traversed
|
|
235
329
|
traverseStack.push(...buildStackEntries(value, fullKey, relativeFullKey, includedFiles))
|
|
236
330
|
continue
|
|
237
331
|
}
|
|
@@ -239,24 +333,24 @@ function loadUserConfigAppYaml () {
|
|
|
239
333
|
if (key === defaults.INCLUDE_DIRECTIVE) {
|
|
240
334
|
// $include: 'configFile', value is string pointing to config file
|
|
241
335
|
// includes are relative to the current config file
|
|
336
|
+
|
|
242
337
|
// config path in index always as unix path, it doesn't matter but makes it easier to generate testing mock data
|
|
243
338
|
const incFile = path.join(path.dirname(currConfigFile), value)
|
|
244
339
|
const configFile = incFile.split(path.sep).join(path.posix.sep)
|
|
245
|
-
// const configFile = upath.toUnix(path.join(path.dirname(currConfigFile), value))
|
|
246
340
|
|
|
247
341
|
// 1. check for include cycles
|
|
248
342
|
if (includedFiles.includes(configFile)) {
|
|
249
343
|
throw new Error(`Detected '${defaults.INCLUDE_DIRECTIVE}' cycle: '${[...includedFiles, configFile].toString()}', please make sure that your configuration has no cycles.`)
|
|
250
344
|
}
|
|
251
345
|
// 2. check if file exists
|
|
252
|
-
if (!configCache[configFile] && !fs.
|
|
346
|
+
if (!configCache[configFile] && !(await fs.exists(configFile))) {
|
|
253
347
|
throw new Error(`'${defaults.INCLUDE_DIRECTIVE}: ${configFile}' cannot be resolved, please make sure the file exists.`)
|
|
254
348
|
}
|
|
255
349
|
// 3. delete the $include directive to be replaced
|
|
256
350
|
delete parentObj[key]
|
|
257
351
|
// 4. load the included file
|
|
258
|
-
// Note the included file can in turn also have includes
|
|
259
|
-
const loadedConfig = configCache[configFile] || yaml.safeLoad(fs.
|
|
352
|
+
// Note the included file can in turn also have includes, so we will have to traverse it as well
|
|
353
|
+
const loadedConfig = configCache[configFile] || yaml.safeLoad(await fs.readFile(configFile, 'utf8'))
|
|
260
354
|
if (Array.isArray(loadedConfig) || typeof loadedConfig !== 'object') {
|
|
261
355
|
throw new Error(`'${defaults.INCLUDE_DIRECTIVE}: ${configFile}' does not resolve to an object. Including an array or primitive type config is not supported.`)
|
|
262
356
|
}
|
|
@@ -274,13 +368,14 @@ function loadUserConfigAppYaml () {
|
|
|
274
368
|
// else primitive types: do nothing
|
|
275
369
|
}
|
|
276
370
|
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
371
|
+
const appConfigWithIncludeIndex = { config, includeIndex }
|
|
372
|
+
rewritePathsInPlace(appConfigWithIncludeIndex, { absolutePaths, appRoot })
|
|
373
|
+
|
|
374
|
+
return appConfigWithIncludeIndex
|
|
280
375
|
}
|
|
281
376
|
|
|
282
377
|
/** @private */
|
|
283
|
-
function
|
|
378
|
+
async function legacyToAppConfig (commonConfig) {
|
|
284
379
|
// load legacy user app config from manifest.yml, package.json, .aio.app
|
|
285
380
|
const includeIndex = {}
|
|
286
381
|
const legacyAppConfig = {}
|
|
@@ -289,7 +384,9 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
289
384
|
// todo: new value usingLegacyConfig
|
|
290
385
|
// this module should not console.log/warn ... or include chalk ...
|
|
291
386
|
if (commonConfig.aio.cna !== undefined || commonConfig.aio.app !== undefined) {
|
|
292
|
-
|
|
387
|
+
// this might have never have been seen in the wild as we don't know
|
|
388
|
+
// what log-level users have set
|
|
389
|
+
aioLogger.error('App config in \'.aio\' file is deprecated. Please move your \'.aio.app\' or \'.aio.cna\' to \'app.config.yaml\'.')
|
|
293
390
|
const appConfig = { ...commonConfig.aio.app, ...commonConfig.aio.cna }
|
|
294
391
|
Object.entries(appConfig).forEach(([k, v]) => {
|
|
295
392
|
legacyAppConfig[k] = v
|
|
@@ -298,8 +395,8 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
298
395
|
}
|
|
299
396
|
|
|
300
397
|
// 2. load legacy manifest.yaml
|
|
301
|
-
if (fs.
|
|
302
|
-
const runtimeManifest = yaml.safeLoad(fs.
|
|
398
|
+
if (await fs.exists(defaults.LEGACY_RUNTIME_MANIFEST)) {
|
|
399
|
+
const runtimeManifest = yaml.safeLoad(await fs.readFile(defaults.LEGACY_RUNTIME_MANIFEST, 'utf8'))
|
|
303
400
|
legacyAppConfig.runtimeManifest = runtimeManifest
|
|
304
401
|
// populate index
|
|
305
402
|
const baseKey = `${defaults.APPLICATION_CONFIG_KEY}.runtimeManifest`
|
|
@@ -321,30 +418,17 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
321
418
|
if (pkgjsonscripts) {
|
|
322
419
|
const hooks = {}
|
|
323
420
|
// 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]
|
|
421
|
+
|
|
422
|
+
HookKeys.forEach(hookKey => {
|
|
423
|
+
if (pkgjsonscripts[hookKey]) {
|
|
424
|
+
hooks[hookKey] = pkgjsonscripts[hookKey]
|
|
425
|
+
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}.hooks.${hookKey}`] = { file: 'package.json', key: `scripts.${hookKey}` }
|
|
343
426
|
}
|
|
344
427
|
})
|
|
428
|
+
|
|
345
429
|
// todo: new val usingLegacyHooks:Boolean
|
|
346
430
|
if (Object.keys(hooks).length > 0) {
|
|
347
|
-
aioLogger.
|
|
431
|
+
aioLogger.error('hooks in \'package.json\' are deprecated. Please move your hooks to \'app.config.yaml\' under the \'hooks\' key')
|
|
348
432
|
legacyAppConfig.hooks = hooks
|
|
349
433
|
// build index
|
|
350
434
|
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}.hooks`] = { file: 'package.json', key: 'scripts' }
|
|
@@ -358,80 +442,126 @@ function loadUserConfigLegacy (commonConfig) {
|
|
|
358
442
|
}
|
|
359
443
|
}
|
|
360
444
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
445
|
+
const appConfigWithIncludeIndex = { includeIndex, config: { [defaults.APPLICATION_CONFIG_KEY]: legacyAppConfig } }
|
|
446
|
+
if (Object.keys(includeIndex).length <= 0) {
|
|
447
|
+
// no legacy configuration return now
|
|
448
|
+
// todo return undefined here and for normal config, rewrite load and merge logic
|
|
449
|
+
return appConfigWithIncludeIndex
|
|
364
450
|
}
|
|
365
451
|
|
|
366
|
-
|
|
452
|
+
// add the top key
|
|
453
|
+
includeIndex[`${defaults.APPLICATION_CONFIG_KEY}`] = { file: '.aio', key: 'app' }
|
|
454
|
+
|
|
455
|
+
/* always absolute paths for now. Note that if we were interested in relative
|
|
456
|
+
paths there would be no need to rewrite, as all paths are
|
|
457
|
+
defined in the app root folder for legacy apps */
|
|
458
|
+
rewritePathsInPlace(appConfigWithIncludeIndex, { absolutePaths: true })
|
|
459
|
+
|
|
460
|
+
return appConfigWithIncludeIndex
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** @private */
|
|
464
|
+
function rewritePathsInPlace (appConfigWithIncludeIndex, options) {
|
|
465
|
+
const { config: appConfig, includeIndex } = appConfigWithIncludeIndex
|
|
466
|
+
|
|
467
|
+
const buildStackEntries = (currObj, currFullKey) => Object.keys(currObj || {} /* cover for null */).map(k => {
|
|
468
|
+
const fullKey = currFullKey ? currFullKey + '.' + k : k
|
|
469
|
+
const includedFromConfigFile = includeIndex[fullKey].file
|
|
470
|
+
return { fullKey, includedFromConfigFile, key: k, parentObj: currObj }
|
|
471
|
+
})
|
|
472
|
+
const stack = buildStackEntries(appConfig)
|
|
473
|
+
|
|
474
|
+
while (stack.length > 0) {
|
|
475
|
+
const { fullKey, includedFromConfigFile, key, parentObj } = stack.pop()
|
|
476
|
+
const value = parentObj[key]
|
|
477
|
+
|
|
478
|
+
if (typeof value === 'string' && PATH_KEYS.filter(reg => fullKey.match(reg)).length) {
|
|
479
|
+
// rewrite path value to be relative to the root instead of being relative to the config file that includes it
|
|
480
|
+
parentObj[key] = resolveToRoot(value, includedFromConfigFile, options)
|
|
481
|
+
}
|
|
482
|
+
if (typeof value === 'object') {
|
|
483
|
+
// object or Array
|
|
484
|
+
stack.push(...buildStackEntries(value, fullKey))
|
|
485
|
+
}
|
|
486
|
+
}
|
|
367
487
|
}
|
|
368
488
|
|
|
369
489
|
/** @private */
|
|
370
|
-
function
|
|
371
|
-
// NOTE: here we do a simplified merge, deep merge with copy might be wanted
|
|
490
|
+
async function mergeLegacyAppConfig (appConfigWithIncludeIndex, legacyAppConfigWithIncludeIndex) {
|
|
491
|
+
// NOTE: here we do a simplified merge, deep merge with copy might be wanted
|
|
372
492
|
|
|
373
|
-
// only need to merge application configs as legacy config system
|
|
374
|
-
const
|
|
375
|
-
const
|
|
493
|
+
// only need to merge application configs as legacy config system does not work with extensions
|
|
494
|
+
const application = appConfigWithIncludeIndex.config[defaults.APPLICATION_CONFIG_KEY]
|
|
495
|
+
const legacyApplication = legacyAppConfigWithIncludeIndex.config[defaults.APPLICATION_CONFIG_KEY]
|
|
376
496
|
|
|
377
497
|
// merge 1 level config fields, such as 'actions': 'path/to/actions', precedence for new config
|
|
378
|
-
const
|
|
498
|
+
const mergedApplication = { ...legacyApplication, ...application }
|
|
379
499
|
|
|
380
500
|
// special cases if both are defined
|
|
381
|
-
if (
|
|
501
|
+
if (application && legacyApplication) {
|
|
382
502
|
// for simplicity runtimeManifest is not merged, it's one or the other
|
|
383
|
-
if (
|
|
503
|
+
if (legacyApplication.runtimeManifest && application.runtimeManifest) {
|
|
384
504
|
aioLogger.warn('\'manifest.yml\' is ignored in favor of key \'runtimeManifest\' in \'app.config.yaml\'.')
|
|
385
505
|
}
|
|
386
506
|
// hooks are merged
|
|
387
|
-
if (
|
|
388
|
-
|
|
507
|
+
if (legacyApplication.hooks && application.hooks) {
|
|
508
|
+
mergedApplication.hooks = { ...legacyApplication.hooks, ...application.hooks }
|
|
389
509
|
}
|
|
390
510
|
}
|
|
391
511
|
|
|
392
512
|
return {
|
|
393
|
-
|
|
394
|
-
|
|
513
|
+
config: {
|
|
514
|
+
...appConfigWithIncludeIndex.config,
|
|
515
|
+
[defaults.APPLICATION_CONFIG_KEY]: mergedApplication
|
|
516
|
+
},
|
|
517
|
+
// new configuration index takes precedence
|
|
518
|
+
includeIndex: { ...legacyAppConfigWithIncludeIndex.includeIndex, ...appConfigWithIncludeIndex.includeIndex }
|
|
395
519
|
}
|
|
396
520
|
}
|
|
397
521
|
|
|
398
522
|
/** @private */
|
|
399
|
-
function buildAllConfigs (userConfig, commonConfig, includeIndex) {
|
|
523
|
+
async function buildAllConfigs (userConfig, commonConfig, includeIndex) {
|
|
400
524
|
return {
|
|
401
|
-
...buildAppConfig(userConfig, commonConfig, includeIndex),
|
|
402
|
-
...buildExtConfigs(userConfig, commonConfig, includeIndex)
|
|
525
|
+
...(await buildAppConfig(userConfig, commonConfig, includeIndex)),
|
|
526
|
+
...(await buildExtConfigs(userConfig, commonConfig, includeIndex))
|
|
403
527
|
}
|
|
404
528
|
}
|
|
405
529
|
|
|
406
530
|
/** @private */
|
|
407
|
-
function buildExtConfigs (userConfig, commonConfig, includeIndex) {
|
|
531
|
+
async function buildExtConfigs (userConfig, commonConfig, includeIndex) {
|
|
408
532
|
const configs = {}
|
|
409
533
|
if (userConfig[defaults.EXTENSIONS_CONFIG_KEY]) {
|
|
410
|
-
Object.entries(userConfig[defaults.EXTENSIONS_CONFIG_KEY])
|
|
411
|
-
|
|
534
|
+
const entries = Object.entries(userConfig[defaults.EXTENSIONS_CONFIG_KEY])
|
|
535
|
+
for (const [extName, singleUserConfig] of entries) {
|
|
536
|
+
configs[extName] = await buildSingleConfig(extName, singleUserConfig, commonConfig, includeIndex)
|
|
412
537
|
// extensions have an extra operations field
|
|
413
538
|
configs[extName].operations = singleUserConfig.operations
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
|
|
539
|
+
// this is checked by the schema validation
|
|
540
|
+
// if (!configs[extName].operations) {
|
|
541
|
+
// throw new Error(`Missing 'operations' config field for extension point ${extName}`)
|
|
542
|
+
// }
|
|
543
|
+
}
|
|
418
544
|
}
|
|
419
545
|
return configs
|
|
420
546
|
}
|
|
421
547
|
|
|
422
548
|
/** @private */
|
|
423
|
-
function buildAppConfig (userConfig, commonConfig, includeIndex) {
|
|
424
|
-
const fullAppConfig = buildSingleConfig(defaults.APPLICATION_CONFIG_KEY,
|
|
549
|
+
async function buildAppConfig (userConfig, commonConfig, includeIndex) {
|
|
550
|
+
const fullAppConfig = await buildSingleConfig(defaults.APPLICATION_CONFIG_KEY,
|
|
551
|
+
userConfig[defaults.APPLICATION_CONFIG_KEY],
|
|
552
|
+
commonConfig,
|
|
553
|
+
includeIndex)
|
|
425
554
|
|
|
555
|
+
// todo: this needs to be updated; an app doesn't exist if there is no config.
|
|
426
556
|
if (!fullAppConfig.app.hasBackend && !fullAppConfig.app.hasFrontend) {
|
|
427
|
-
// only set application config if there is an
|
|
557
|
+
// only set application config if there is an actual app, meaning either some backend or frontend
|
|
428
558
|
return {}
|
|
429
559
|
}
|
|
430
560
|
return { [defaults.APPLICATION_CONFIG_KEY]: fullAppConfig }
|
|
431
561
|
}
|
|
432
562
|
|
|
433
563
|
/** @private */
|
|
434
|
-
function buildSingleConfig (configName, singleUserConfig, commonConfig, includeIndex) {
|
|
564
|
+
async function buildSingleConfig (configName, singleUserConfig, commonConfig, includeIndex) {
|
|
435
565
|
// used as subfolder folder in dist, converts to a single dir, e.g. dx/excshell/1 =>
|
|
436
566
|
// dx-excshell-1 and dist/dx-excshell-1/actions/action-xyz.zip
|
|
437
567
|
const subFolderName = configName.replace(/\//g, '-')
|
|
@@ -455,28 +585,32 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
455
585
|
return config
|
|
456
586
|
}
|
|
457
587
|
|
|
458
|
-
|
|
459
|
-
// The default action and web path are relative to the folder holding the config file.
|
|
588
|
+
// Default paths are relative to the folder holding the config file.
|
|
460
589
|
// Let's search the config path that defines a key in the same config object level as 'web' or
|
|
461
590
|
// 'action'
|
|
462
|
-
const
|
|
463
|
-
const
|
|
464
|
-
|
|
465
|
-
const
|
|
591
|
+
const otherKeyInObject = Object.keys(singleUserConfig)[0]
|
|
592
|
+
const configFilePath = includeIndex[`${fullKeyPrefix}.${otherKeyInObject}`].file
|
|
593
|
+
|
|
594
|
+
const defaultActionPath = resolveToRoot('actions/', configFilePath)
|
|
595
|
+
const defaultWebPath = resolveToRoot('web-src/', configFilePath)
|
|
596
|
+
const defaultUnitTestPath = resolveToRoot('test/', configFilePath)
|
|
597
|
+
const defaultE2eTestPath = resolveToRoot('e2e/', configFilePath)
|
|
466
598
|
const defaultDistPath = 'dist/' // relative to root
|
|
467
599
|
|
|
468
600
|
// absolute paths
|
|
469
|
-
const actions =
|
|
470
|
-
const unitTest =
|
|
471
|
-
const e2eTest =
|
|
472
|
-
const dist =
|
|
601
|
+
const actions = singleUserConfig.actions || defaultActionPath
|
|
602
|
+
const unitTest = singleUserConfig.unitTest || defaultUnitTestPath
|
|
603
|
+
const e2eTest = singleUserConfig.e2eTest || defaultE2eTestPath
|
|
604
|
+
const dist = singleUserConfig.dist || defaultDistPath
|
|
473
605
|
|
|
606
|
+
// web src folder might be defined in 'web' key or 'web.src' key
|
|
474
607
|
let web
|
|
475
|
-
if (
|
|
476
|
-
|
|
477
|
-
|
|
608
|
+
if (typeof singleUserConfig.web === 'string') {
|
|
609
|
+
web = singleUserConfig.web
|
|
610
|
+
} else if (typeof singleUserConfig.web === 'object') {
|
|
611
|
+
web = singleUserConfig.web.src || defaultWebPath
|
|
478
612
|
} else {
|
|
479
|
-
web =
|
|
613
|
+
web = defaultWebPath
|
|
480
614
|
}
|
|
481
615
|
|
|
482
616
|
config.tests.unit = path.resolve(unitTest)
|
|
@@ -485,15 +619,22 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
485
619
|
const manifest = singleUserConfig.runtimeManifest
|
|
486
620
|
|
|
487
621
|
config.app.hasBackend = !!manifest
|
|
488
|
-
config.app.hasFrontend = fs.
|
|
622
|
+
config.app.hasFrontend = await fs.exists(web)
|
|
489
623
|
config.app.dist = path.resolve(dist, dist === defaultDistPath ? subFolderName : '')
|
|
490
624
|
|
|
625
|
+
if (singleUserConfig.events) {
|
|
626
|
+
config.events = { ...singleUserConfig.events }
|
|
627
|
+
}
|
|
628
|
+
if (commonConfig?.aio?.project) {
|
|
629
|
+
config.project = commonConfig.aio.project
|
|
630
|
+
}
|
|
631
|
+
|
|
491
632
|
// actions
|
|
492
633
|
config.actions.src = path.resolve(actions) // needed for app add first action
|
|
493
634
|
if (config.app.hasBackend) {
|
|
494
635
|
config.actions.dist = path.join(config.app.dist, 'actions')
|
|
495
|
-
config.manifest = { src: 'manifest.yml' } // even
|
|
496
|
-
config.manifest.full =
|
|
636
|
+
config.manifest = { src: 'manifest.yml' } // even for non legacy config paths, it is required for runtime sync
|
|
637
|
+
config.manifest.full = cloneDeep(manifest)
|
|
497
638
|
config.manifest.packagePlaceholder = '__APP_PACKAGE__'
|
|
498
639
|
config.manifest.package = config.manifest.full.packages && config.manifest.full.packages[config.manifest.packagePlaceholder]
|
|
499
640
|
if (config.manifest.package) {
|
|
@@ -548,43 +689,27 @@ function buildSingleConfig (configName, singleUserConfig, commonConfig, includeI
|
|
|
548
689
|
return config
|
|
549
690
|
}
|
|
550
691
|
|
|
551
|
-
/** @private */
|
|
552
|
-
function rewriteRuntimeManifestPathsToRelRoot (manifestConfig, fullKeyToManifest, includeIndex) {
|
|
553
|
-
const manifestCopy = cloneDeep(manifestConfig)
|
|
554
|
-
|
|
555
|
-
Object.entries(manifestCopy.packages || {}).forEach(([pkgName, pkg]) => {
|
|
556
|
-
Object.entries(pkg.actions || {}).forEach(([actionName, action]) => {
|
|
557
|
-
const fullKeyToAction = `${fullKeyToManifest}.packages.${pkgName}.actions.${actionName}`
|
|
558
|
-
if (action.function) {
|
|
559
|
-
// absolut path
|
|
560
|
-
action.function = pathConfigValueToAbs(action.function, fullKeyToAction + '.function', includeIndex)
|
|
561
|
-
}
|
|
562
|
-
if (action.include) {
|
|
563
|
-
action.include.forEach((arr, i) => {
|
|
564
|
-
// absolut path
|
|
565
|
-
action.include[i][0] = pathConfigValueToAbs(action.include[i][0], fullKeyToAction + `.include.${i}.0`, includeIndex)
|
|
566
|
-
})
|
|
567
|
-
}
|
|
568
|
-
})
|
|
569
|
-
})
|
|
570
|
-
|
|
571
|
-
return manifestCopy
|
|
572
|
-
}
|
|
573
|
-
|
|
574
692
|
// Because of the $include directives, config paths (e.g actions: './path/to/actions') can
|
|
575
693
|
// be relative to config files in any subfolder. Config keys that define path values are
|
|
576
694
|
// identified and their value is rewritten relative to the root folder.
|
|
577
695
|
/** @private */
|
|
578
|
-
function
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
696
|
+
function resolveToRoot (pathValue, includedFromConfigPath, options = {}) {
|
|
697
|
+
// path.resolve => support both absolute pathValue and relative (relative joins with
|
|
698
|
+
// config dir and process.cwd, absolute returns pathValue)
|
|
699
|
+
if (options.absolutePaths) {
|
|
700
|
+
return path.resolve(path.dirname(includedFromConfigPath), pathValue)
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// relative paths
|
|
704
|
+
if (options.appRoot) {
|
|
705
|
+
// make sure path is relative to appRoot and not cwd
|
|
706
|
+
includedFromConfigPath = path.relative(options.appRoot, includedFromConfigPath)
|
|
582
707
|
}
|
|
583
|
-
|
|
584
|
-
const configPath = configData.file
|
|
585
|
-
// path.resolve => support both absolut pathValue and relative (relative joins with
|
|
586
|
-
// config dir and process.cwd, absolut returns pathValue)
|
|
587
|
-
return path.resolve(path.dirname(configPath), pathValue)
|
|
708
|
+
return path.join(path.dirname(includedFromConfigPath), pathValue).split(path.sep).join(path.posix.sep)
|
|
588
709
|
}
|
|
589
710
|
|
|
590
|
-
module.exports =
|
|
711
|
+
module.exports = {
|
|
712
|
+
load,
|
|
713
|
+
validate,
|
|
714
|
+
coalesce
|
|
715
|
+
}
|