@adobe/aio-cli-plugin-app 8.2.0 → 8.5.0

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.
@@ -9,7 +9,7 @@ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTA
9
9
  OF ANY KIND, either express or implied. See the License for the specific language
10
10
  governing permissions and limitations under the License.
11
11
  */
12
-
12
+ const upath = require('upath')
13
13
  const chokidar = require('chokidar')
14
14
  const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:actions-watcher', { provider: 'debug' })
15
15
  const buildActions = require('./build-actions')
@@ -58,12 +58,13 @@ module.exports = async (watcherOptions) => {
58
58
  * Builds and deploy the app.
59
59
  *
60
60
  * @param {WatcherOptions} watcherOptions the options for the watcher
61
+ * @param {Array<string>} filterActions add filters to deploy only specified OpenWhisk actions
61
62
  */
62
- async function buildAndDeploy (watcherOptions) {
63
+ async function buildAndDeploy (watcherOptions, filterActions) {
63
64
  const { config, isLocal, log } = watcherOptions
64
65
 
65
- await buildActions(config)
66
- await deployActions(config, isLocal, log)
66
+ await buildActions(config, filterActions)
67
+ await deployActions(config, isLocal, log, filterActions)
67
68
  }
68
69
 
69
70
  /**
@@ -75,30 +76,62 @@ async function buildAndDeploy (watcherOptions) {
75
76
  function createChangeHandler (watcherOptions) {
76
77
  const { watcher, log } = watcherOptions
77
78
 
78
- let running = false
79
- let changed = false
79
+ let deploymentInProgress = false
80
+ let fileChanged = false
81
+ let undeployedFile = ''
80
82
 
81
83
  return async (filePath) => {
82
- if (running) {
84
+ aioLogger.debug('Code change triggered...')
85
+ if (deploymentInProgress) {
83
86
  aioLogger.debug(`${filePath} has changed. Deploy in progress. This change will be deployed after completion of current deployment.`)
84
- changed = true
87
+ undeployedFile = filePath
88
+ fileChanged = true
85
89
  return
86
90
  }
87
- running = true
91
+ deploymentInProgress = true
88
92
  try {
89
93
  aioLogger.debug(`${filePath} has changed. Redeploying actions.`)
90
- await buildAndDeploy(watcherOptions)
91
- aioLogger.debug('Deployment successful.')
94
+ const filterActions = getActionNameFromPath(filePath, watcherOptions)
95
+ if (!filterActions.length) {
96
+ log(' -> A non-action file was changed, restart is required to deploy...')
97
+ } else {
98
+ await buildAndDeploy(watcherOptions, filterActions)
99
+ aioLogger.debug('Deployment successful')
100
+ }
92
101
  } catch (err) {
93
102
  log(' -> Error encountered while deploying actions. Stopping auto refresh.')
94
103
  aioLogger.debug(err)
95
104
  await watcher.close()
96
105
  }
97
- if (changed) {
106
+ if (fileChanged) {
98
107
  aioLogger.debug('Code changed during deployment. Triggering deploy again.')
99
- changed = running = false
100
- await createChangeHandler(watcherOptions)(filePath)
108
+ fileChanged = deploymentInProgress = false
109
+ await createChangeHandler(watcherOptions)(undeployedFile)
101
110
  }
102
- running = false
111
+ deploymentInProgress = false
103
112
  }
104
113
  }
114
+
115
+ /**
116
+ * Util function which returns the actionName from the filePath.
117
+ *
118
+ * @param {string} filePath path of the file
119
+ * @param {WatcherOptions} watcherOptions the options for the watcher
120
+ * @returns {Array<string>} All of the actions which match the modified path
121
+ */
122
+ function getActionNameFromPath (filePath, watcherOptions) {
123
+ const actionNames = []
124
+ const unixFilePath = upath.toUnix(filePath)
125
+ const { config } = watcherOptions
126
+ Object.entries(config.manifest.full.packages).forEach(([, pkg]) => {
127
+ if (pkg.actions) {
128
+ Object.entries(pkg.actions).forEach(([actionName, action]) => {
129
+ const unixActionFunction = upath.toUnix(action.function)
130
+ if (unixActionFunction.includes(unixFilePath)) {
131
+ actionNames.push(actionName)
132
+ }
133
+ })
134
+ }
135
+ })
136
+ return actionNames
137
+ }
@@ -17,12 +17,13 @@ const { buildActions } = require('@adobe/aio-lib-runtime')
17
17
  * Builds actions.
18
18
  *
19
19
  * @param {object} config see src/lib/config-loader.js
20
+ * @param {Array<string>} filterActions add filters to deploy only specified OpenWhisk actions
20
21
  */
21
- module.exports = async (config) => {
22
+ module.exports = async (config, filterActions) => {
22
23
  utils.runScript(config.hooks['pre-app-build'])
23
24
  const script = await utils.runScript(config.hooks['build-actions'])
24
25
  if (!script) {
25
- await buildActions(config)
26
+ await buildActions(config, filterActions)
26
27
  }
27
28
  utils.runScript(config.hooks['post-app-build'])
28
29
  }
@@ -17,15 +17,22 @@ const { deployActions } = require('@adobe/aio-lib-runtime')
17
17
  * Deploys actions.
18
18
  *
19
19
  * @param {object} config see src/lib/config-loader.js
20
- * @param {boolean} isLocal=false set to true if it's a local deploy
20
+ * @param {boolean} isLocal default false, set to true if it's a local deploy
21
21
  * @param {Function} [log] a log function
22
+ * @param {boolean} filter true if a filter by built actions is desired.
22
23
  */
23
24
  /** @private */
24
- module.exports = async (config, isLocal = false, log = () => {}) => {
25
+ module.exports = async (config, isLocal = false, log = () => {}, filter = false) => {
25
26
  utils.runScript(config.hooks['pre-app-deploy'])
26
27
  const script = await utils.runScript(config.hooks['deploy-actions'])
27
28
  if (!script) {
28
- const entities = await deployActions(config, { isLocalDev: isLocal }, log)
29
+ const deployConfig = {
30
+ isLocalDev: isLocal,
31
+ filterEntities: {
32
+ byBuiltActions: filter
33
+ }
34
+ }
35
+ const entities = await deployActions(config, deployConfig, log)
29
36
  if (entities.actions) {
30
37
  const web = entities.actions.filter(utils.createWebExportFilter(true))
31
38
  const nonWeb = entities.actions.filter(utils.createWebExportFilter(false))
@@ -0,0 +1,267 @@
1
+ /*
2
+ Copyright 2020 Adobe. All rights reserved.
3
+ This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License. You may obtain a copy
5
+ of the License at http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software distributed under
8
+ the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ OF ANY KIND, either express or implied. See the License for the specific language
10
+ governing permissions and limitations under the License.
11
+ */
12
+
13
+ const rtLib = require('@adobe/aio-lib-runtime')
14
+ const { writeAio, writeEnv } = require('./import')
15
+ const crypto = require('crypto')
16
+ const fs = require('fs-extra')
17
+ const path = require('path')
18
+
19
+ const SECRET_FIELD_TYPE = 'password'
20
+ const CHECKSUM_DIR = 'dist'
21
+ const CHECKSUM_FILE = 'log-forwarding-config.sha256'
22
+ const IGNORED_REMOTE_SETTINGS = ['updated_at']
23
+
24
+ class LogForwarding {
25
+ constructor (aioConfig) {
26
+ this.aioConfig = aioConfig
27
+ }
28
+
29
+ async init () {
30
+ rtLib.utils.checkOpenWhiskCredentials({ ow: this.aioConfig.runtime })
31
+ this.logForwarding = await getRTLogForwarding(this.aioConfig.runtime)
32
+ return this
33
+ }
34
+
35
+ getLocalConfig () {
36
+ const config = this.aioConfig.project.workspace.log_forwarding
37
+ try {
38
+ return this.getConfigFromJson(config)
39
+ } catch (e) {
40
+ throw new Error('Incorrect local log forwarding configuration. ' + e.message)
41
+ }
42
+ }
43
+
44
+ getLocalConfigWithSecrets () {
45
+ const config = this.getLocalConfig()
46
+ const destination = config.getDestination()
47
+ const settings = config.getSettings()
48
+ if (config.isDefined()) {
49
+ const destinationSettings = this.logForwarding.getDestinationSettings(destination)
50
+ const missingSecrets = []
51
+ destinationSettings.forEach(e => {
52
+ if (e.type === SECRET_FIELD_TYPE) {
53
+ const secretVarName = getSecretVarName(destination, e.name)
54
+ if (process.env[secretVarName] !== undefined) {
55
+ settings[e.name] = process.env[secretVarName]
56
+ } else {
57
+ missingSecrets.push(secretVarName)
58
+ }
59
+ }
60
+ })
61
+ if (missingSecrets.length > 0) {
62
+ throw new Error('Required secrets are missing in environment variables: ' + missingSecrets.join(', ') + '. ' +
63
+ 'Make sure these variables are set in .env file')
64
+ }
65
+ }
66
+ return new LogForwardingConfig(destination, settings)
67
+ }
68
+
69
+ async getServerConfig () {
70
+ try {
71
+ return this.getConfigFromJson(await this.logForwarding.get())
72
+ } catch (e) {
73
+ throw new Error('Incorrect log forwarding configuration on server. ' + e.message)
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Convert JSON config to Log Forwarding object
79
+ *
80
+ * @param {object} configJson Config in JSON format
81
+ * @returns {LogForwardingConfig} Config
82
+ */
83
+ getConfigFromJson (configJson) {
84
+ let destination
85
+ let settings
86
+
87
+ if (configJson !== undefined && configJson !== null && !Array.isArray(configJson) && typeof configJson === 'object') {
88
+ const destinations = Object.keys(configJson)
89
+ if (destinations.length === 1) {
90
+ destination = destinations[0]
91
+ settings = configJson[destination]
92
+ } else {
93
+ throw new Error(`Configuration has ${destinations.length} destinations. Exactly one must be defined.`)
94
+ }
95
+ }
96
+ return new LogForwardingConfig(destination, settings)
97
+ }
98
+
99
+ getSupportedDestinations () {
100
+ return this.logForwarding.getSupportedDestinations()
101
+ }
102
+
103
+ getSettingsConfig (destination) {
104
+ return this.logForwarding.getDestinationSettings(destination)
105
+ }
106
+
107
+ async updateLocalConfig (lfConfig) {
108
+ const destination = lfConfig.getDestination()
109
+ const destinationSettings = this.logForwarding.getDestinationSettings(destination)
110
+ const projectConfig = {
111
+ project: this.aioConfig.project
112
+ }
113
+
114
+ const nonSecretSettings = {}
115
+ const secretSettings = {}
116
+
117
+ const settings = lfConfig.getSettings()
118
+ Object.keys(settings)
119
+ .filter(e => !IGNORED_REMOTE_SETTINGS.includes(e))
120
+ .forEach(k => {
121
+ const destFieldSettings = destinationSettings.find(i => i.name === k)
122
+ if (destFieldSettings.type === SECRET_FIELD_TYPE) {
123
+ secretSettings[getSecretVarName(destination, k)] = settings[k]
124
+ } else {
125
+ nonSecretSettings[k] = settings[k]
126
+ }
127
+ })
128
+
129
+ projectConfig.project.workspace.log_forwarding = {
130
+ [destination]: nonSecretSettings
131
+ }
132
+ const interactive = false
133
+ const merge = true
134
+ await writeAio(projectConfig, '', { interactive, merge })
135
+ await writeEnv({}, '', { interactive, merge }, secretSettings)
136
+ }
137
+
138
+ isLocalConfigChanged () {
139
+ if (fs.pathExistsSync(path.join(CHECKSUM_DIR, CHECKSUM_FILE))) {
140
+ const oldChecksum = fs.readFileSync(path.join(CHECKSUM_DIR, CHECKSUM_FILE)).toString()
141
+ const config = this.getLocalConfigWithSecrets()
142
+ const newChecksum = getChecksum(config)
143
+ return oldChecksum !== newChecksum
144
+ } else {
145
+ return true
146
+ }
147
+ }
148
+
149
+ async updateServerConfig (lfConfig) {
150
+ const res = await this.logForwarding.setDestination(lfConfig.getDestination(), lfConfig.getSettings())
151
+ const checksum = getChecksum(lfConfig)
152
+ fs.ensureDirSync(CHECKSUM_DIR)
153
+ fs.writeFile(path.join(CHECKSUM_DIR, CHECKSUM_FILE), checksum, { flags: 'w' })
154
+ return res
155
+ }
156
+ }
157
+
158
+ class LogForwardingConfig {
159
+ constructor (destination, settings) {
160
+ this.destination = destination
161
+ this.settings = settings
162
+ }
163
+
164
+ getDestination () {
165
+ return this.destination
166
+ }
167
+
168
+ getSettings () {
169
+ return this.settings
170
+ }
171
+
172
+ getMergedConfig (config) {
173
+ const newSettings = {}
174
+ Object.keys(this.settings).forEach(k => {
175
+ newSettings[k] = config.settings[k] !== undefined ? config.settings[k] : this.settings[k]
176
+ })
177
+ return new LogForwardingConfig(this.destination, newSettings)
178
+ }
179
+
180
+ isDefined () {
181
+ return this.destination !== undefined
182
+ }
183
+
184
+ isDefault () {
185
+ return !this.isDefined() || this.getDestination() === 'adobe_io_runtime'
186
+ }
187
+
188
+ isEqual (config) {
189
+ return (this.isDefault() && config.isDefault()) ||
190
+ (this.destination === config.getDestination() && shallowEqual(this.settings, config.settings))
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Init Log Forwarding
196
+ *
197
+ * @param {object} aioConfig aio Config
198
+ * @returns {Promise<LogForwarding>} Log Forwarding
199
+ */
200
+ async function init (aioConfig) {
201
+ const lf = new LogForwarding(aioConfig)
202
+ return await lf.init()
203
+ }
204
+
205
+ /**
206
+ * Get Runtime Log Forwarding
207
+ *
208
+ * @param {object} rtConfig Runtime config
209
+ * @returns {Promise<LogForwarding>} Log Forwarding
210
+ */
211
+ async function getRTLogForwarding (rtConfig) {
212
+ const rt = await rtLib.init({
213
+ ...rtConfig,
214
+ api_key: rtConfig.auth
215
+ })
216
+ return rt.logForwarding
217
+ }
218
+
219
+ /**
220
+ * Compare to log forwarding configs
221
+ *
222
+ * @param {LogForwardingConfig} config1 Config
223
+ * @param {LogForwardingConfig} config2 Config
224
+ * @returns {boolean} Are configs equal
225
+ */
226
+ function shallowEqual (config1, config2) {
227
+ // updated_at exists on server only and does not impact actual configuration
228
+ const keys1 = Object.keys(config1).filter(e => !IGNORED_REMOTE_SETTINGS.includes(e))
229
+ const keys2 = Object.keys(config2).filter(e => !IGNORED_REMOTE_SETTINGS.includes(e))
230
+ if (keys1.length !== keys2.length) {
231
+ return false
232
+ }
233
+ for (const key of keys1) {
234
+ if (config1[key] !== config2[key]) {
235
+ return false
236
+ }
237
+ }
238
+ return true
239
+ }
240
+
241
+ /**
242
+ * Get secret variable name for the given destination and settings field
243
+ *
244
+ * @param {string} destination Destination
245
+ * @param {string} fieldName Field name
246
+ * @returns {string} Variable name
247
+ */
248
+ function getSecretVarName (destination, fieldName) {
249
+ return destination.toUpperCase() + '__' + fieldName.toUpperCase()
250
+ }
251
+
252
+ /**
253
+ * Generate checksum for the config
254
+ *
255
+ * @param {LogForwardingConfig} config Config
256
+ * @returns {string} Checksum
257
+ */
258
+ function getChecksum (config) {
259
+ return crypto.createHash('sha256')
260
+ .update(JSON.stringify(config))
261
+ .digest('hex')
262
+ }
263
+
264
+ module.exports = {
265
+ init,
266
+ LogForwardingConfig
267
+ }
@@ -122,7 +122,7 @@ async function runDev (config, dataDir, options = {}, log = () => {}) {
122
122
  // Deploy Phase - deploy actions
123
123
  if (withBackend) {
124
124
  log('redeploying actions..')
125
- await deployActions(devConfig, isLocal, log)
125
+ await deployActions(devConfig, isLocal, log, true)
126
126
  }
127
127
 
128
128
  // Deploy Phase - serve the web UI
package/src/lib/vscode.js CHANGED
@@ -47,7 +47,9 @@ function update (config) {
47
47
  'app-config': config,
48
48
  'env-file': config.envFile,
49
49
  'frontend-url': props.frontEndUrl,
50
- 'skip-prompt': true
50
+ 'skip-prompt': true,
51
+ // by default yeoman runs the install, we control installation from the app plugin
52
+ 'skip-install': true
51
53
  }
52
54
  })
53
55
  await env.runGenerator(gen)