@adobe/aio-cli-plugin-app 9.2.0 → 10.0.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,26 +9,24 @@ OF ANY KIND, either express or implied. See the License for the specific languag
9
9
  governing permissions and limitations under the License.
10
10
  */
11
11
 
12
- const AddCommand = require('../../AddCommand')
12
+ const TemplatesCommand = require('../../TemplatesCommand')
13
13
  const yeoman = require('yeoman-environment')
14
14
  const path = require('path')
15
15
  const fs = require('fs-extra')
16
16
  const ora = require('ora')
17
17
  const chalk = require('chalk')
18
- // const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:init', { provider: 'debug' })
19
18
  const { Flags } = require('@oclif/core')
20
19
  const generators = require('@adobe/generator-aio-app')
20
+ const TemplateRegistryAPI = require('@adobe/aio-lib-templates')
21
+ const inquirer = require('inquirer')
21
22
  const hyperlinker = require('hyperlinker')
22
23
 
23
24
  const { loadAndValidateConfigFile, importConfigJson } = require('../../lib/import')
24
- const { atLeastOne } = require('../../lib/app-helper')
25
-
26
- const { ENTP_INT_CERTS_FOLDER, SERVICE_API_KEY_ENV, implPromptChoices } = require('../../lib/defaults')
27
- const cloneDeep = require('lodash.clonedeep')
25
+ const { SERVICE_API_KEY_ENV } = require('../../lib/defaults')
28
26
 
29
27
  const DEFAULT_WORKSPACE = 'Stage'
30
28
 
31
- class InitCommand extends AddCommand {
29
+ class InitCommand extends TemplatesCommand {
32
30
  async run () {
33
31
  const { args, flags } = await this.parse(InitCommand)
34
32
 
@@ -36,19 +34,26 @@ class InitCommand extends AddCommand {
36
34
  this.error('--no-login and --workspace flags cannot be used together.')
37
35
  }
38
36
 
37
+ // check that the template plugin has been installed
38
+ const command = await this.config.findCommand('templates:install')
39
+ if (!command) {
40
+ this.error('aio-cli plugin @adobe/aio-cli-plugin-app-templates was not found. This plugin is required to install templates.')
41
+ }
42
+
39
43
  if (flags.import) {
40
44
  // resolve to absolute path before any chdir
41
45
  flags.import = path.resolve(flags.import)
42
46
  }
43
47
 
44
- if (args.path !== '.') {
45
- const destDir = path.resolve(args.path)
48
+ const destDir = this.destDir(args)
49
+ if (destDir !== '.') {
46
50
  fs.ensureDirSync(destDir)
47
51
  process.chdir(destDir)
48
52
  }
49
53
 
50
54
  const spinner = ora()
51
- if (flags.import || !flags.login) {
55
+ const noLogin = flags.import || !flags.login
56
+ if (noLogin) {
52
57
  // import a console config - no login required!
53
58
  await this.initNoLogin(flags)
54
59
  } else {
@@ -60,7 +65,32 @@ class InitCommand extends AddCommand {
60
65
  await this.runInstallPackages(flags, spinner)
61
66
 
62
67
  this.log(chalk.bold(chalk.green('✔ App initialization finished!')))
63
- this.log('> Tip: you can add more actions, web-assets and events to your project via the `aio app add` commands')
68
+ this.log(`> Tip: you can add more actions, web-assets and events to your project via the '${this.config.bin} app add' commands`)
69
+
70
+ if (noLogin) {
71
+ this.log(`> Run '${this.config.bin} templates info --required-services' to list the required services for the installed templates`)
72
+ }
73
+ }
74
+
75
+ getInitialGenerators (flags) {
76
+ // TODO read from config to override
77
+ const initialGenerators = ['base-app', 'add-ci']
78
+
79
+ if (flags['standalone-app']) {
80
+ initialGenerators.push('application')
81
+ }
82
+
83
+ return initialGenerators
84
+ }
85
+
86
+ /** @private */
87
+ destDir (args) {
88
+ let destDir = '.'
89
+ if (args.path !== '.') {
90
+ destDir = path.resolve(args.path)
91
+ }
92
+
93
+ return destDir
64
94
  }
65
95
 
66
96
  /** @private */
@@ -72,24 +102,29 @@ class InitCommand extends AddCommand {
72
102
  this.log(chalk.green(`Loaded Adobe Developer Console configuration file for the Project '${consoleConfig.project.title}' in the Organization '${consoleConfig.project.org.name}'`))
73
103
  }
74
104
 
75
- // 2. prompt for extension points to be implemented
76
- const extensionPoints = await this.selectExtensionPoints(flags)
105
+ // 2. prompt for templates to be installed
106
+ const templates = await this.getTemplatesForFlags(flags)
107
+ // If no templates selected, init a standalone app
108
+ if (templates.length <= 0) {
109
+ flags['standalone-app'] = true
110
+ }
77
111
 
78
- // 3. run extension point code generators
112
+ // 3. run base code generators
79
113
  const projectName = (consoleConfig && consoleConfig.project.name) || path.basename(process.cwd())
80
- await this.runCodeGenerators(flags, extensionPoints, projectName)
114
+ await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, projectName)
115
+
116
+ // 4. install templates
117
+ await this.installTemplates({
118
+ useDefaultValues: flags.yes,
119
+ installNpm: flags.install,
120
+ installConfig: flags.login,
121
+ templates
122
+ })
81
123
 
82
- // 4. import config - if any
124
+ // 5. import config - if any
83
125
  if (flags.import) {
84
126
  await this.importConsoleConfig(consoleConfig)
85
127
  }
86
-
87
- // 5. This flow supports non logged in users so we can't now for sure if the project has
88
- // required services installed. So we output a note on required services instead.
89
- const requiredServices = this.getAllRequiredServicesFromExtPoints(extensionPoints)
90
- if (requiredServices.length > 0) {
91
- this.log(chalk.bold(`Please ensure the following service(s) are enabled in the Organization and added to the Console Workspace: '${requiredServices}'`))
92
- }
93
128
  }
94
129
 
95
130
  async initWithLogin (flags) {
@@ -104,31 +139,60 @@ class InitCommand extends AddCommand {
104
139
  const project = await this.selectOrCreateConsoleProject(consoleCLI, org)
105
140
  // 4. retrieve workspace details, defaults to Stage
106
141
  const workspace = await this.retrieveWorkspaceFromName(consoleCLI, org, project, flags)
107
- // 5. ask for exensionPoints, only allow selection for extensions that have services enabled in Org
108
- const extensionPoints = await this.selectExtensionPoints(flags, orgSupportedServices)
109
- // 6. add any required services to Workspace
110
- const requiredServices = this.getAllRequiredServicesFromExtPoints(extensionPoints)
111
- await this.addServices(
112
- consoleCLI,
113
- org,
114
- project,
115
- workspace,
116
- orgSupportedServices,
117
- requiredServices
118
- )
119
142
 
120
- // 7. download workspace config
143
+ // 5. get list of templates to install
144
+ const templates = await this.getTemplatesForFlags(flags, orgSupportedServices)
145
+ // If no templates selected, init a standalone app
146
+ if (templates.length <= 0) {
147
+ flags['standalone-app'] = true
148
+ }
149
+
150
+ // 6. download workspace config
121
151
  const consoleConfig = await consoleCLI.getWorkspaceConfig(org.id, project.id, workspace.id, orgSupportedServices)
122
152
 
123
- // 8. run code generators
124
- await this.runCodeGenerators(flags, extensionPoints, consoleConfig.project.name)
153
+ // 7. run base code generators
154
+ await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, consoleConfig.project.name)
125
155
 
126
- // 9. import config
156
+ // 8. import config
127
157
  await this.importConsoleConfig(consoleConfig)
128
158
 
159
+ // 9. install templates
160
+ await this.installTemplates({
161
+ useDefaultValues: flags.yes,
162
+ installNpm: flags.install,
163
+ templates
164
+ })
165
+
129
166
  this.log(chalk.blue(chalk.bold(`Project initialized for Workspace ${workspace.name}, you can run 'aio app use -w <workspace>' to switch workspace.`)))
130
167
  }
131
168
 
169
+ async getTemplatesForFlags (flags, orgSupportedServices = null) {
170
+ if (flags.template) {
171
+ return flags.template
172
+ } else if (flags.extension) {
173
+ const { notFound, templates: extensionTemplates } = await this.getTemplatesByExtensionPointIds(flags.extension)
174
+ if (notFound.length > 0) {
175
+ this.error(`Extension(s) '${notFound.join(', ')}' not found in the Template Registry.`)
176
+ }
177
+ return extensionTemplates.map(t => t.name)
178
+ } else if (!flags['standalone-app']) {
179
+ const noLogin = flags.import || !flags.login
180
+ let [searchCriteria, orderByCriteria] = await this.getSearchCriteria(orgSupportedServices)
181
+ if (noLogin) {
182
+ searchCriteria = {
183
+ [TemplateRegistryAPI.SEARCH_CRITERIA_STATUSES]: TemplateRegistryAPI.TEMPLATE_STATUS_APPROVED,
184
+ [TemplateRegistryAPI.SEARCH_CRITERIA_CATEGORIES]: TemplateRegistryAPI.SEARCH_CRITERIA_FILTER_NOT + 'helper-template'
185
+ }
186
+ orderByCriteria = {
187
+ [TemplateRegistryAPI.ORDER_BY_CRITERIA_PUBLISH_DATE]: TemplateRegistryAPI.ORDER_BY_CRITERIA_SORT_DESC
188
+ }
189
+ }
190
+ return this.selectTemplates(searchCriteria, orderByCriteria, orgSupportedServices)
191
+ } else {
192
+ return []
193
+ }
194
+ }
195
+
132
196
  async ensureDevTermAccepted (consoleCLI, orgId) {
133
197
  const isTermAccepted = await consoleCLI.checkDevTermsForOrg(orgId)
134
198
  if (!isTermAccepted) {
@@ -147,40 +211,65 @@ class InitCommand extends AddCommand {
147
211
  }
148
212
  }
149
213
 
150
- async selectExtensionPoints (flags, orgSupportedServices = null) {
151
- if (!flags.extensions) {
152
- return [implPromptChoices.find(i => i.value.name === 'application').value]
153
- } else if (flags.extension) {
154
- const extList = implPromptChoices.filter(i => flags.extension.indexOf(i.value.name) > -1)
155
- .map(i => i.value)
156
- if (extList.length < 1) {
157
- throw new Error(`--extension=${flags.extension} not found.`)
214
+ async getSearchCriteria (orgSupportedServices) {
215
+ const choices = [
216
+ {
217
+ name: 'All Templates',
218
+ value: 'allTemplates',
219
+ checked: true
220
+ },
221
+ {
222
+ name: 'All Extension Points',
223
+ value: 'allExtensionPoints',
224
+ checked: false
158
225
  }
159
- return extList
160
- } else {
161
- const choices = cloneDeep(implPromptChoices)
162
-
163
- // disable extensions that lack required services
164
- if (orgSupportedServices) {
165
- const supportedServiceCodes = new Set(orgSupportedServices.map(s => s.code))
166
- // filter choices
167
- choices.forEach(c => {
168
- const missingServices = c.value.requiredServices.filter(s => !supportedServiceCodes.has(s))
169
- if (missingServices.length > 0) {
170
- c.disabled = true
171
- c.name = `${c.name}: missing service(s) in Org: '${missingServices}'`
172
- }
173
- })
226
+ ]
227
+
228
+ if (orgSupportedServices) {
229
+ choices.push({
230
+ name: 'Only Templates Supported By My Org',
231
+ value: 'orgTemplates',
232
+ checked: false
233
+ })
234
+ }
235
+
236
+ const { components: selection } = await inquirer.prompt([
237
+ {
238
+ type: 'list',
239
+ name: 'components',
240
+ message: 'What templates do you want to search for?',
241
+ loop: false,
242
+ choices
174
243
  }
175
- const answers = await this.prompt([{
176
- type: 'checkbox',
177
- name: 'res',
178
- message: 'Which extension point(s) do you wish to implement ?',
179
- choices,
180
- validate: atLeastOne
181
- }])
182
- return answers.res
244
+ ])
245
+
246
+ const searchCriteria = {
247
+ [TemplateRegistryAPI.SEARCH_CRITERIA_STATUSES]: TemplateRegistryAPI.TEMPLATE_STATUS_APPROVED,
248
+ [TemplateRegistryAPI.SEARCH_CRITERIA_CATEGORIES]: TemplateRegistryAPI.SEARCH_CRITERIA_FILTER_NOT + 'helper-template'
183
249
  }
250
+
251
+ switch (selection) {
252
+ case 'orgTemplates': {
253
+ const supportedServiceCodes = new Set(orgSupportedServices.map(s => `|${s.code}`)) // | symbol denotes an OR clause
254
+ searchCriteria[TemplateRegistryAPI.SEARCH_CRITERIA_APIS] = Array.from(supportedServiceCodes)
255
+ }
256
+ break
257
+ case 'allExtensionPoints':
258
+ searchCriteria[TemplateRegistryAPI.SEARCH_CRITERIA_EXTENSIONS] = TemplateRegistryAPI.SEARCH_CRITERIA_FILTER_ANY
259
+ break
260
+ case 'allTemplates':
261
+ default:
262
+ break
263
+ }
264
+
265
+ const { name: selectionLabel } = choices.find(item => item.value === selection)
266
+
267
+ // an optional OrderBy Criteria object
268
+ const orderByCriteria = {
269
+ [TemplateRegistryAPI.ORDER_BY_CRITERIA_PUBLISH_DATE]: TemplateRegistryAPI.ORDER_BY_CRITERIA_SORT_DESC
270
+ }
271
+
272
+ return [searchCriteria, orderByCriteria, selection, selectionLabel]
184
273
  }
185
274
 
186
275
  async selectConsoleOrg (consoleCLI) {
@@ -227,84 +316,21 @@ class InitCommand extends AddCommand {
227
316
  return workspace
228
317
  }
229
318
 
230
- async addServices (consoleCLI, org, project, workspace, orgSupportedServices, requiredServices) {
231
- // add required services if needed (for extension point)
232
- const currServiceProperties = await consoleCLI.getServicePropertiesFromWorkspace(
233
- org.id,
234
- project.id,
235
- workspace,
236
- orgSupportedServices
237
- )
238
- const serviceCodesToAdd = requiredServices.filter(s => !currServiceProperties.some(sp => sp.sdkCode === s))
239
- if (serviceCodesToAdd.length > 0) {
240
- const servicePropertiesToAdd = serviceCodesToAdd
241
- .map(s => {
242
- // previous check ensures orgSupportedServices has required services
243
- const orgServiceDefinition = orgSupportedServices.find(os => os.code === s)
244
- return {
245
- sdkCode: s,
246
- name: orgServiceDefinition.name,
247
- roles: orgServiceDefinition.properties.roles,
248
- // add all licenseConfigs
249
- licenseConfigs: orgServiceDefinition.properties.licenseConfigs
250
- }
251
- })
252
- await consoleCLI.subscribeToServices(
253
- org.id,
254
- project,
255
- workspace,
256
- // certDir if need to create integration
257
- path.join(this.config.dataDir, ENTP_INT_CERTS_FOLDER),
258
- // new service properties
259
- currServiceProperties.concat(servicePropertiesToAdd)
260
- )
261
- }
262
- return workspace
263
- }
264
-
265
- getAllRequiredServicesFromExtPoints (extensionPoints) {
266
- const requiredServicesWithDuplicates = extensionPoints
267
- .map(e => e.requiredServices)
268
- // flat not supported in node 10
269
- .reduce((res, arr) => res.concat(arr), [])
270
- return [...new Set(requiredServicesWithDuplicates)]
271
- }
272
-
273
- async runCodeGenerators (flags, extensionPoints, projectName) {
274
- let env = yeoman.createEnv()
275
- // by default yeoman runs the install, we control installation from the app plugin
319
+ async runCodeGenerators (generatorNames, skipPrompt, projectName) {
320
+ const env = yeoman.createEnv()
276
321
  env.options = { skipInstall: true }
277
- const initialGenerators = ['base-app', 'add-ci']
322
+
278
323
  // first run app generator that will generate the root skeleton + ci
279
- for (const generatorKey of initialGenerators) {
324
+ for (const generatorKey of generatorNames) {
280
325
  const appGen = env.instantiate(generators[generatorKey], {
281
326
  options: {
282
- 'skip-prompt': flags.yes,
283
- 'project-name': projectName
327
+ 'skip-prompt': skipPrompt,
328
+ 'project-name': projectName,
329
+ force: true
284
330
  }
285
331
  })
286
332
  await env.runGenerator(appGen)
287
333
  }
288
-
289
- // Creating new Yeoman env here to workaround an issue where yeoman reuses the conflicter from previous environment.
290
- // https://github.com/yeoman/environment/issues/324
291
-
292
- env = yeoman.createEnv()
293
- // by default yeoman runs the install, we control installation from the app plugin
294
- env.options = { skipInstall: true }
295
- // try to use appGen.composeWith
296
- for (let i = 0; i < extensionPoints.length; ++i) {
297
- const extGen = env.instantiate(
298
- extensionPoints[i].generator,
299
- {
300
- options: {
301
- 'skip-prompt': flags.yes,
302
- // do not prompt for overwrites
303
- force: true
304
- }
305
- })
306
- await env.runGenerator(extGen)
307
- }
308
334
  }
309
335
 
310
336
  // console config is already loaded into object
@@ -329,7 +355,7 @@ InitCommand.description = `Create a new Adobe I/O App
329
355
  `
330
356
 
331
357
  InitCommand.flags = {
332
- ...AddCommand.flags,
358
+ ...TemplatesCommand.flags,
333
359
  yes: Flags.boolean({
334
360
  description: 'Skip questions, and use all default values',
335
361
  default: false,
@@ -344,16 +370,21 @@ InitCommand.flags = {
344
370
  default: true,
345
371
  allowNo: true
346
372
  }),
347
- extensions: Flags.boolean({
348
- description: 'Use --no-extensions to create a blank application that does not integrate with Exchange',
349
- default: true,
350
- allowNo: true
351
- }),
352
373
  extension: Flags.string({
353
374
  description: 'Extension point(s) to implement',
354
375
  char: 'e',
355
376
  multiple: true,
356
- exclusive: ['extensions']
377
+ exclusive: ['template']
378
+ }),
379
+ 'standalone-app': Flags.boolean({
380
+ description: 'Create a stand-alone application',
381
+ default: false,
382
+ exclusive: ['template']
383
+ }),
384
+ template: Flags.string({
385
+ description: 'Specify a link to a template that will be installed',
386
+ char: 't',
387
+ multiple: true
357
388
  }),
358
389
  workspace: Flags.string({
359
390
  description: 'Specify the Adobe Developer Console Workspace to init from, defaults to Stage',
@@ -33,8 +33,6 @@ class Run extends BaseCommand {
33
33
  async run () {
34
34
  // cli input
35
35
  const { flags } = await this.parse(Run)
36
- // aliases
37
- flags.actions = flags.actions && !flags['skip-actions']
38
36
 
39
37
  const spinner = ora()
40
38
 
@@ -64,8 +62,8 @@ class Run extends BaseCommand {
64
62
  if (!hasBackend && !hasFrontend) {
65
63
  this.error(new Error('nothing to run.. there is no frontend and no manifest.yml, are you in a valid app?'))
66
64
  }
67
- if (flags['skip-actions'] && !hasFrontend) {
68
- this.error(new Error('nothing to run.. there is no frontend and --skip-actions is set'))
65
+ if (!flags.actions && !hasFrontend) {
66
+ this.error(new Error('nothing to run.. there is no frontend and --no-actions is set'))
69
67
  }
70
68
 
71
69
  const runOptions = {
@@ -203,22 +201,16 @@ Run.description = 'Run an Adobe I/O App'
203
201
  Run.flags = {
204
202
  ...BaseCommand.flags,
205
203
  local: Flags.boolean({
206
- description: 'Run/debug actions locally ( requires Docker running )',
207
- exclusive: ['skip-actions']
204
+ description: 'Run/debug actions locally (requires Docker running)',
205
+ exclusive: ['no-actions']
208
206
  }),
209
207
  serve: Flags.boolean({
210
208
  description: '[default: true] Start frontend server (experimental)',
211
209
  default: true,
212
210
  allowNo: true
213
211
  }),
214
- 'skip-actions': Flags.boolean({
215
- description: '[deprecated] Please use --no-actions',
216
- exclusive: ['local'],
217
- default: false
218
- }),
219
212
  actions: Flags.boolean({
220
213
  description: '[default: true] Run actions, defaults to true, to skip actions use --no-actions',
221
- exclusive: ['local'], // no-actions and local don't work together
222
214
  default: true,
223
215
  allowNo: true
224
216
  }),
@@ -26,10 +26,6 @@ class Undeploy extends BaseCommand {
26
26
  // cli input
27
27
  const { flags } = await this.parse(Undeploy)
28
28
 
29
- // flags
30
- flags['web-assets'] = flags['web-assets'] && !flags['skip-static'] && !flags['skip-web-assets'] && !flags.action
31
- flags.actions = flags.actions && !flags['skip-actions']
32
-
33
29
  const undeployConfigs = this.getAppExtConfigs(flags)
34
30
  let libConsoleCLI
35
31
  if (flags.unpublish) {
@@ -146,15 +142,6 @@ Undeploy.description = `Undeploys an Adobe I/O App
146
142
 
147
143
  Undeploy.flags = {
148
144
  ...BaseCommand.flags,
149
- 'skip-static': Flags.boolean({
150
- description: '[deprecated] Please use --no-web-assets'
151
- }),
152
- 'skip-web-assets': Flags.boolean({
153
- description: '[deprecated] Please use --no-web-assets'
154
- }),
155
- 'skip-actions': Flags.boolean({
156
- description: '[deprecated] Please use --no-actions'
157
- }),
158
145
  actions: Flags.boolean({
159
146
  description: '[default: true] Undeploy actions if any',
160
147
  default: true,
@@ -10,7 +10,6 @@ governing permissions and limitations under the License.
10
10
  */
11
11
 
12
12
  // defaults & constants
13
- const generators = require('@adobe/generator-aio-app')
14
13
 
15
14
  module.exports = {
16
15
  defaultAppHostname: 'adobeio-static.net',
@@ -35,35 +34,5 @@ module.exports = {
35
34
  LEGACY_RUNTIME_MANIFEST: 'manifest.yml',
36
35
  INCLUDE_DIRECTIVE: '$include',
37
36
  APPLICATION_CONFIG_KEY: 'application',
38
- EXTENSIONS_CONFIG_KEY: 'extensions',
39
-
40
- implPromptChoices: [
41
- // we abuse the extension command to also let users add a standalone app
42
- {
43
- name: 'Standalone Application',
44
- value: {
45
- name: 'application',
46
- generator: generators.application,
47
- requiredServices: [] // TODO required services should be filled based on selected actions
48
- }
49
- },
50
- // extensions
51
- // TODO this list should not be hardcoded but fetched from xt reg
52
- {
53
- name: 'DX Experience Cloud SPA v1',
54
- value: {
55
- name: 'dx/excshell/1',
56
- generator: generators.extensions['dx/excshell/1'],
57
- requiredServices: []
58
- }
59
- },
60
- {
61
- name: 'DX Asset Compute Worker v1',
62
- value: {
63
- name: 'dx/asset-compute/worker/1',
64
- generator: generators.extensions['dx/asset-compute/worker/1'],
65
- requiredServices: ['AssetComputeSDK']
66
- }
67
- }
68
- ]
37
+ EXTENSIONS_CONFIG_KEY: 'extensions'
69
38
  }