@adobe/aio-cli-plugin-app 10.7.2 → 11.1.0-pre.2024-01-09.2e4115c3

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 CHANGED
@@ -1,24 +1,25 @@
1
1
  {
2
2
  "name": "@adobe/aio-cli-plugin-app",
3
3
  "description": "Create, Build and Deploy Adobe I/O Applications",
4
- "version": "10.7.2",
4
+ "version": "11.1.0-pre.2024-01-09.2e4115c3",
5
5
  "author": "Adobe Inc.",
6
6
  "bugs": "https://github.com/adobe/aio-cli-plugin-app/issues",
7
7
  "dependencies": {
8
- "@adobe/aio-cli-lib-app-config": "^1.1.0",
8
+ "@adobe/aio-cli-lib-app-config": "next",
9
9
  "@adobe/aio-cli-lib-app-config-next": "npm:@adobe/aio-cli-lib-app-config@^3.0.0",
10
- "@adobe/aio-cli-lib-console": "^4.1.0",
11
- "@adobe/aio-lib-core-config": "^4.0.0",
12
- "@adobe/aio-lib-core-logging": "^2.0.0",
13
- "@adobe/aio-lib-core-networking": "^4.1.0",
14
- "@adobe/aio-lib-env": "^2.0.0",
15
- "@adobe/aio-lib-ims": "^6.0.0",
16
- "@adobe/aio-lib-runtime": "^5.0.0",
10
+ "@adobe/aio-cli-lib-console": "next",
11
+ "@adobe/aio-lib-core-config": "next",
12
+ "@adobe/aio-lib-core-logging": "next",
13
+ "@adobe/aio-lib-core-networking": "next",
14
+ "@adobe/aio-lib-env": "next",
15
+ "@adobe/aio-lib-ims": "next",
16
+ "@adobe/aio-lib-runtime": "next",
17
17
  "@adobe/aio-lib-templates": "^2.2.0",
18
- "@adobe/aio-lib-web": "^6.1.0",
19
- "@adobe/generator-aio-app": "^5.1.0",
20
- "@adobe/generator-app-common-lib": "^0.4.5",
18
+ "@adobe/aio-lib-web": "next",
19
+ "@adobe/generator-aio-app": "next",
20
+ "@adobe/generator-app-common-lib": "^1.0.0",
21
21
  "@adobe/inquirer-table-checkbox": "^1.2.0",
22
+ "@octokit/rest": "^19.0.11",
22
23
  "@oclif/core": "^2.11.6",
23
24
  "@parcel/core": "^2.7.0",
24
25
  "@parcel/reporter-cli": "^2.7.0",
@@ -48,12 +49,13 @@
48
49
  "term-size": "^2.2.1",
49
50
  "unzipper": "^0.10.11",
50
51
  "upath": "^2",
52
+ "junk": "^3.1.0",
51
53
  "which": "^3.0.0",
52
54
  "yeoman-environment": "^3.2.0"
53
55
  },
54
56
  "devDependencies": {
55
57
  "@adobe/aio-lib-test-proxy": "^1.0.0",
56
- "@adobe/eslint-config-aio-lib-config": "^2.0.1",
58
+ "@adobe/eslint-config-aio-lib-config": "^2.0.2",
57
59
  "@types/jest": "^29",
58
60
  "babel-runtime": "^6.26.0",
59
61
  "core-js": "^3",
@@ -73,7 +75,7 @@
73
75
  "typescript": "^5.1.6"
74
76
  },
75
77
  "engines": {
76
- "node": "^14.18 || ^16.13 || >=18"
78
+ "node": ">=18"
77
79
  },
78
80
  "files": [
79
81
  "bin/run",
@@ -106,5 +108,6 @@
106
108
  },
107
109
  "bin": {
108
110
  "aio-next": "./bin/run"
109
- }
110
- }
111
+ },
112
+ "prereleaseSha": "2e4115c3814afd05bf049bc66b1a491cc5833a6a"
113
+ }
@@ -35,20 +35,7 @@ class BaseCommand extends Command {
35
35
  async catch (error) {
36
36
  const { flags } = await this.parse(this.prototype)
37
37
  aioLogger.error(error) // debug log
38
- this.handleError(error, flags.verbose)
39
- }
40
-
41
- handleError (error, verbose) {
42
- const errorMessages = ['no such file or directory', 'find configuration']
43
- if (errorMessages.find(msg => error.message.includes(msg))) {
44
- const errorList = [
45
- 'Not a valid application root folder.',
46
- 'Please run \'aio app\' commands from a folder generated by aio app init',
47
- verbose ? error.stack : error.message
48
- ]
49
- this.error(errorList.join('\n'))
50
- }
51
- this.error(error.message)
38
+ this.error(flags.verbose ? error.stack : error.message)
52
39
  }
53
40
 
54
41
  async init () {
@@ -11,12 +11,9 @@ governing permissions and limitations under the License.
11
11
 
12
12
  const AddCommand = require('../../../AddCommand')
13
13
  const TemplatesCommand = require('../../../TemplatesCommand')
14
- const yeoman = require('yeoman-environment')
15
14
  const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:add:event', { provider: 'debug' })
16
15
  const { Flags } = require('@oclif/core')
17
- const ora = require('ora')
18
16
  const path = require('path')
19
- const generators = require('@adobe/generator-aio-app')
20
17
  const TemplateRegistryAPI = require('@adobe/aio-lib-templates')
21
18
 
22
19
  class AddEventCommand extends TemplatesCommand {
@@ -24,7 +21,6 @@ class AddEventCommand extends TemplatesCommand {
24
21
  const { flags } = await this.parse(AddEventCommand)
25
22
 
26
23
  aioLogger.debug(`add events with flags: ${JSON.stringify(flags)}`)
27
- const spinner = ora()
28
24
 
29
25
  // guaranteed to have at least one, otherwise would throw in config load or in matching the ext name
30
26
  const entries = Object.entries(this.getAppExtConfigs(flags))
@@ -36,42 +32,27 @@ class AddEventCommand extends TemplatesCommand {
36
32
  const config = entries[0][1]
37
33
  const actionFolder = path.relative(config.root, config.actions.src)
38
34
  const runtimeManifestData = this.getRuntimeManifestConfigFile(configName)
39
-
35
+ const eventsData = this.getEventsConfigFile(configName)
40
36
  const templateOptions = {
41
37
  'skip-prompt': false,
42
38
  'action-folder': actionFolder,
43
39
  'config-path': runtimeManifestData.file,
44
- 'full-key-to-manifest': runtimeManifestData.key
40
+ 'full-key-to-manifest': runtimeManifestData.key,
41
+ 'full-key-to-events-manifest': eventsData.key,
42
+ 'events-config-path': eventsData.file
45
43
  }
46
44
 
47
- if (flags['experimental-allow-events-templates']) {
48
- const eventsData = this.getEventsConfigFile(configName)
49
- templateOptions['full-key-to-events-manifest'] = eventsData.key
50
- templateOptions['events-config-path'] = eventsData.file
51
- const [searchCriteria, orderByCriteria] = await this.getSearchCriteria()
52
- const templates = await this.selectTemplates(searchCriteria, orderByCriteria)
53
- if (templates.length === 0) {
54
- this.error('No events templates were chosen to be installed.')
55
- } else {
56
- await this.installTemplates({
57
- useDefaultValues: flags.yes,
58
- installNpm: flags.install,
59
- templateOptions,
60
- templates
61
- })
62
- }
63
- // by default yeoman runs the install, we control installation from the app plugin
45
+ const [searchCriteria, orderByCriteria] = await this.getSearchCriteria()
46
+ const templates = await this.selectTemplates(searchCriteria, orderByCriteria)
47
+ if (templates.length === 0) {
48
+ this.error('No events templates were chosen to be installed.')
64
49
  } else {
65
- templateOptions['skip-prompt'] = flags.yes
66
- templateOptions.force = true
67
- const env = yeoman.createEnv()
68
- env.options = { skipInstall: true }
69
- const eventsGen = env.instantiate(generators['add-events'], {
70
- options: templateOptions
50
+ await this.installTemplates({
51
+ useDefaultValues: flags.yes,
52
+ installNpm: flags.install,
53
+ templateOptions,
54
+ templates
71
55
  })
72
- await env.runGenerator(eventsGen)
73
-
74
- await this.runInstallPackages(flags, spinner)
75
56
  }
76
57
  }
77
58
 
@@ -107,10 +88,6 @@ AddEventCommand.flags = {
107
88
  multiple: false,
108
89
  parse: str => [str]
109
90
  }),
110
- 'experimental-allow-events-templates': Flags.boolean({
111
- description: 'Feature flag to enable events templates. NOTE: skip-prompt will have no effect if this flag is enabled.',
112
- default: false
113
- }),
114
91
  ...AddCommand.flags
115
92
  }
116
93
 
@@ -116,6 +116,8 @@ class Build extends BaseCommand {
116
116
  shouldOptimize: flags['web-optimize'],
117
117
  logLevel: flags.verbose ? 'verbose' : 'warn'
118
118
  }
119
+ // empty the dist folder to prevent an S3 explosion
120
+ fs.emptyDirSync(config.web.distProd)
119
121
  const bundler = await bundle(entries, config.web.distProd, bundleOptions, onProgress)
120
122
  await bundler.run()
121
123
  spinner.succeed(chalk.green(`Building web assets for '${name}'`))
@@ -22,6 +22,9 @@ const { createWebExportFilter, runInProcess, buildExtensionPointPayloadWoMetadat
22
22
  const rtLib = require('@adobe/aio-lib-runtime')
23
23
  const LogForwarding = require('../../lib/log-forwarding')
24
24
 
25
+ const PRE_DEPLOY_EVENT_REG = 'pre-deploy-event-reg'
26
+ const POST_DEPLOY_EVENT_REG = 'post-deploy-event-reg'
27
+
25
28
  class Deploy extends BuildCommand {
26
29
  async run () {
27
30
  // cli input
@@ -142,13 +145,10 @@ class Deploy extends BuildCommand {
142
145
 
143
146
  try {
144
147
  await runInProcess(config.hooks['pre-app-deploy'], config)
145
- if (flags['feature-event-hooks']) {
146
- this.log('feature-event-hooks is enabled, running pre-deploy-event-reg hook')
147
- const hookResults = await this.config.runHook('pre-deploy-event-reg', { appConfig: config, force: flags['force-events'] })
148
- if (hookResults?.failures?.length > 0) {
149
- // output should be "Error : <plugin-name> : <error-message>\n" for each failure
150
- this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
151
- }
148
+ const hookResults = await this.config.runHook(PRE_DEPLOY_EVENT_REG, { appConfig: config, force: flags['force-events'] })
149
+ if (hookResults?.failures?.length > 0) {
150
+ // output should be "Error : <plugin-name> : <error-message>\n" for each failure
151
+ this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
152
152
  }
153
153
  } catch (err) {
154
154
  this.error(err)
@@ -250,13 +250,10 @@ class Deploy extends BuildCommand {
250
250
 
251
251
  try {
252
252
  await runInProcess(config.hooks['post-app-deploy'], config)
253
- if (flags['feature-event-hooks']) {
254
- this.log('feature-event-hooks is enabled, running post-deploy-event-reg hook')
255
- const hookResults = await this.config.runHook('post-deploy-event-reg', { appConfig: config, force: flags['force-events'] })
256
- if (hookResults?.failures?.length > 0) {
257
- // output should be "Error : <plugin-name> : <error-message>\n" for each failure
258
- this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
259
- }
253
+ const hookResults = await this.config.runHook(POST_DEPLOY_EVENT_REG, { appConfig: config, force: flags['force-events'] })
254
+ if (hookResults?.failures?.length > 0) {
255
+ // output should be "Error : <plugin-name> : <error-message>\n" for each failure
256
+ this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
260
257
  }
261
258
  } catch (err) {
262
259
  this.error(err)
@@ -353,10 +350,9 @@ Deploy.flags = {
353
350
  exclusive: ['action', 'publish'] // no-publish is excluded
354
351
  }),
355
352
  'force-events': Flags.boolean({
356
- description: '[default: false] Force event registrations and overwrite any previous registrations',
353
+ description: '[default: false] Force event registrations and delete any registrations not part of the config file',
357
354
  default: false,
358
355
  allowNo: true,
359
- dependsOn: ['feature-event-hooks'],
360
356
  exclusive: ['action', 'publish'] // no-publish is excluded
361
357
  }),
362
358
  'web-optimize': Flags.boolean({
@@ -367,12 +363,6 @@ Deploy.flags = {
367
363
  description: '[default: true] Update log forwarding configuration on server',
368
364
  default: true,
369
365
  allowNo: true
370
- }),
371
- 'feature-event-hooks': Flags.boolean({
372
- description: '[default: false] Enable event hooks feature',
373
- default: false,
374
- allowNo: true,
375
- hidden: true
376
366
  })
377
367
  }
378
368
 
@@ -20,9 +20,10 @@ const generators = require('@adobe/generator-aio-app')
20
20
  const TemplateRegistryAPI = require('@adobe/aio-lib-templates')
21
21
  const inquirer = require('inquirer')
22
22
  const hyperlinker = require('hyperlinker')
23
-
24
23
  const { importConsoleConfig } = require('../../lib/import')
25
24
  const { loadAndValidateConfigFile } = require('../../lib/import-helper')
25
+ const { Octokit } = require('@octokit/rest')
26
+ const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:init', { provider: 'debug' })
26
27
 
27
28
  const DEFAULT_WORKSPACE = 'Stage'
28
29
 
@@ -102,24 +103,28 @@ class InitCommand extends TemplatesCommand {
102
103
  this.log(chalk.green(`Loaded Adobe Developer Console configuration file for the Project '${consoleConfig.project.title}' in the Organization '${consoleConfig.project.org.name}'`))
103
104
  }
104
105
 
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
- }
106
+ if (flags.repo) {
107
+ await this.withQuickstart(flags.repo)
108
+ } else {
109
+ // 2. prompt for templates to be installed
110
+ const templates = await this.getTemplatesForFlags(flags)
111
+ // If no templates selected, init a standalone app
112
+ if (templates.length <= 0) {
113
+ flags['standalone-app'] = true
114
+ }
111
115
 
112
- // 3. run base code generators
113
- const projectName = (consoleConfig && consoleConfig.project.name) || path.basename(process.cwd())
114
- await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, projectName)
116
+ // 3. run base code generators
117
+ const projectName = (consoleConfig && consoleConfig.project.name) || path.basename(process.cwd())
118
+ await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, projectName)
115
119
 
116
- // 4. install templates
117
- await this.installTemplates({
118
- useDefaultValues: flags.yes,
119
- installNpm: flags.install,
120
- installConfig: flags.login,
121
- templates
122
- })
120
+ // 4. install templates
121
+ await this.installTemplates({
122
+ useDefaultValues: flags.yes,
123
+ installNpm: flags.install,
124
+ installConfig: flags.login,
125
+ templates
126
+ })
127
+ }
123
128
 
124
129
  // 5. import config - if any
125
130
  if (flags.import) {
@@ -128,40 +133,50 @@ class InitCommand extends TemplatesCommand {
128
133
  }
129
134
 
130
135
  async initWithLogin (flags) {
136
+ if (flags.repo) {
137
+ await this.withQuickstart(flags.repo)
138
+ }
131
139
  // this will trigger a login
132
140
  const consoleCLI = await this.getLibConsoleCLI()
133
141
 
134
142
  // 1. select org
135
- const org = await this.selectConsoleOrg(consoleCLI)
143
+ const org = await this.selectConsoleOrg(consoleCLI, flags)
136
144
  // 2. get supported services
137
145
  const orgSupportedServices = await consoleCLI.getEnabledServicesForOrg(org.id)
138
146
  // 3. select or create project
139
- const project = await this.selectOrCreateConsoleProject(consoleCLI, org)
147
+ const project = await this.selectOrCreateConsoleProject(consoleCLI, org, flags)
140
148
  // 4. retrieve workspace details, defaults to Stage
141
149
  const workspace = await this.retrieveWorkspaceFromName(consoleCLI, org, project, flags)
142
150
 
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
151
+ let templates
152
+ if (!flags.repo) {
153
+ // 5. get list of templates to install
154
+ templates = await this.getTemplatesForFlags(flags, orgSupportedServices)
155
+ // If no templates selected, init a standalone app
156
+ if (templates.length <= 0) {
157
+ flags['standalone-app'] = true
158
+ }
148
159
  }
149
160
 
150
161
  // 6. download workspace config
151
162
  const consoleConfig = await consoleCLI.getWorkspaceConfig(org.id, project.id, workspace.id, orgSupportedServices)
152
163
 
153
164
  // 7. run base code generators
154
- await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, consoleConfig.project.name)
165
+ if (!flags.repo) {
166
+ await this.runCodeGenerators(this.getInitialGenerators(flags), flags.yes, consoleConfig.project.name)
167
+ }
155
168
 
156
169
  // 8. import config
157
170
  await this.importConsoleConfig(consoleConfig, flags)
158
171
 
159
172
  // 9. install templates
160
- await this.installTemplates({
161
- useDefaultValues: flags.yes,
162
- installNpm: flags.install,
163
- templates
164
- })
173
+ if (!flags.repo) {
174
+ await this.installTemplates({
175
+ useDefaultValues: flags.yes,
176
+ installNpm: flags.install,
177
+ templates
178
+ })
179
+ }
165
180
 
166
181
  this.log(chalk.blue(chalk.bold(`Project initialized for Workspace ${workspace.name}, you can run 'aio app use -w <workspace>' to switch workspace.`)))
167
182
  }
@@ -275,21 +290,24 @@ class InitCommand extends TemplatesCommand {
275
290
  return [searchCriteria, orderByCriteria, selection, selectionLabel]
276
291
  }
277
292
 
278
- async selectConsoleOrg (consoleCLI) {
293
+ async selectConsoleOrg (consoleCLI, flags) {
279
294
  const organizations = await consoleCLI.getOrganizations()
280
- const selectedOrg = await consoleCLI.promptForSelectOrganization(organizations)
295
+ const selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
281
296
  await this.ensureDevTermAccepted(consoleCLI, selectedOrg.id)
282
297
  return selectedOrg
283
298
  }
284
299
 
285
- async selectOrCreateConsoleProject (consoleCLI, org) {
300
+ async selectOrCreateConsoleProject (consoleCLI, org, flags) {
286
301
  const projects = await consoleCLI.getProjects(org.id)
287
302
  let project = await consoleCLI.promptForSelectProject(
288
303
  projects,
289
- {},
304
+ { projectId: flags.project, projectName: flags.project },
290
305
  { allowCreate: true }
291
306
  )
292
307
  if (!project) {
308
+ if (flags.project) {
309
+ this.error(`--project ${flags.project} not found`)
310
+ }
293
311
  // user has escaped project selection prompt, let's create a new one
294
312
  const projectDetails = await consoleCLI.promptForCreateProjectDetails()
295
313
  project = await consoleCLI.createProject(org.id, projectDetails)
@@ -348,7 +366,56 @@ class InitCommand extends TemplatesCommand {
348
366
  }
349
367
  )
350
368
  }
369
+
370
+ async withQuickstart (fullRepo) {
371
+ const octokit = new Octokit({
372
+ auth: ''
373
+ })
374
+ const spinner = ora('Downloading quickstart repo').start()
375
+ /** @private */
376
+ async function downloadRepoDirRecursive (owner, repo, filePath, basePath) {
377
+ const { data } = await octokit.repos.getContent({ owner, repo, path: filePath })
378
+ for (const fileOrDir of data) {
379
+ if (fileOrDir.type === 'dir') {
380
+ const destDir = path.relative(basePath, fileOrDir.path)
381
+ fs.ensureDirSync(destDir)
382
+ await downloadRepoDirRecursive(owner, repo, fileOrDir.path, basePath)
383
+ } else {
384
+ // todo: use a spinner
385
+ spinner.text = `Downloading ${fileOrDir.path}`
386
+ const response = await fetch(fileOrDir.download_url)
387
+ const jsonResponse = await response.text()
388
+ fs.writeFileSync(path.relative(basePath, fileOrDir.path), jsonResponse)
389
+ }
390
+ }
391
+ }
392
+ // we need to handle n-deep paths, <owner>/<repo>/<path>/<path>
393
+ const [owner, repo, ...restOfPath] = fullRepo.split('/')
394
+ const basePath = restOfPath.join('/')
395
+ try {
396
+ const response = await octokit.repos.getContent({ owner, repo, path: `${basePath}/app.config.yaml` })
397
+ aioLogger.debug(`github headers: ${JSON.stringify(response.headers, 0, 2)}`)
398
+ await downloadRepoDirRecursive(owner, repo, basePath, basePath)
399
+ spinner.succeed('Downloaded quickstart repo')
400
+ } catch (e) {
401
+ if (e.status === 404) {
402
+ spinner.fail('Quickstart repo not found')
403
+ this.error('--repo does not point to a valid Adobe App Builder app')
404
+ }
405
+ if (e.status === 403) {
406
+ // This is helpful for debugging, but by default we don't show it
407
+ // github rate limit is 60 requests per hour for unauthenticated users
408
+ const resetTime = new Date(e.response.headers['x-ratelimit-reset'] * 1000)
409
+ aioLogger.debug(`too many requests, resetTime : ${resetTime.toLocaleTimeString()}`)
410
+ spinner.fail()
411
+ this.error('too many requests, please try again later')
412
+ } else {
413
+ this.error(e)
414
+ }
415
+ }
416
+ }
351
417
  }
418
+
352
419
  InitCommand.description = `Create a new Adobe I/O App
353
420
  `
354
421
 
@@ -372,18 +439,28 @@ InitCommand.flags = {
372
439
  description: 'Extension point(s) to implement',
373
440
  char: 'e',
374
441
  multiple: true,
375
- exclusive: ['template']
442
+ exclusive: ['template', 'repo']
376
443
  }),
377
444
  'standalone-app': Flags.boolean({
378
445
  description: 'Create a stand-alone application',
379
446
  default: false,
380
- exclusive: ['template']
447
+ exclusive: ['template', 'repo']
381
448
  }),
382
449
  template: Flags.string({
383
450
  description: 'Specify a link to a template that will be installed',
384
451
  char: 't',
385
452
  multiple: true
386
453
  }),
454
+ org: Flags.string({
455
+ description: 'Specify the Adobe Developer Console Org to init from',
456
+ hidden: true,
457
+ exclusive: ['import'] // also no-login
458
+ }),
459
+ project: Flags.string({
460
+ description: 'Specify the Adobe Developer Console Project to init from',
461
+ hidden: true,
462
+ exclusive: ['import'] // also no-login
463
+ }),
387
464
  workspace: Flags.string({
388
465
  description: 'Specify the Adobe Developer Console Workspace to init from, defaults to Stage',
389
466
  default: DEFAULT_WORKSPACE,
@@ -394,6 +471,10 @@ InitCommand.flags = {
394
471
  description: 'Skip and confirm prompt for creating a new workspace',
395
472
  default: false
396
473
  }),
474
+ repo: Flags.string({
475
+ description: 'Init from gh quick-start repo. Expected to be of the form <owner>/<repo>/<path>',
476
+ exclusive: ['template', 'extension', 'standalone-app']
477
+ }),
397
478
  'use-jwt': Flags.boolean({
398
479
  description: 'if the config has both jwt and OAuth Server to Server Credentials (while migrating), prefer the JWT credentials',
399
480
  default: false
@@ -55,7 +55,9 @@ class InstallCommand extends BaseCommand {
55
55
  await this.validateAppConfig(outputPath, USER_CONFIG_FILE)
56
56
  await this.validateDeployConfig(outputPath, DEPLOY_CONFIG_FILE)
57
57
  await this.npmInstall(flags.verbose)
58
- await this.runTests()
58
+ if (flags.tests) {
59
+ await this.runTests()
60
+ }
59
61
  this.spinner.succeed('Install done.')
60
62
  } catch (e) {
61
63
  this.spinner.fail(e.message)
@@ -164,6 +166,11 @@ InstallCommand.flags = {
164
166
  description: 'The packaged app output folder path',
165
167
  char: 'o',
166
168
  default: '.'
169
+ }),
170
+ tests: Flags.boolean({
171
+ description: 'Run packaged app unit tests (e.g. aio app:test)',
172
+ default: true,
173
+ allowNo: true
167
174
  })
168
175
  }
169
176