@adobe/aio-cli-plugin-app 14.7.0 → 14.8.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@adobe/aio-cli-plugin-app",
3
3
  "description": "Create, Build and Deploy Adobe I/O Applications",
4
- "version": "14.7.0",
4
+ "version": "14.8.0",
5
5
  "author": "Adobe Inc.",
6
6
  "bugs": "https://github.com/adobe/aio-cli-plugin-app/issues",
7
7
  "dependencies": {
@@ -19,7 +19,7 @@
19
19
  "@adobe/generator-aio-app": "^9",
20
20
  "@adobe/generator-app-common-lib": "^3",
21
21
  "@adobe/inquirer-table-checkbox": "^2",
22
- "@oclif/core": "^2.11.6",
22
+ "@oclif/core": "^4.9.0",
23
23
  "@octokit/rest": "^19.0.11",
24
24
  "@parcel/core": "^2.7.0",
25
25
  "@parcel/reporter-cli": "^2.7.0",
@@ -51,26 +51,21 @@
51
51
  },
52
52
  "devDependencies": {
53
53
  "@adobe/aio-lib-test-proxy": "^2",
54
- "@adobe/eslint-config-aio-lib-config": "^4.0.0",
54
+ "@adobe/eslint-config-aio-lib-config": "^5.0.0",
55
55
  "@types/jest": "^29",
56
56
  "babel-runtime": "^6.26.0",
57
57
  "core-js": "^3",
58
58
  "eol": "^0.9.1",
59
- "eslint": "^8.57.1",
60
- "eslint-config-standard": "^17.1.0",
61
- "eslint-plugin-import": "^2.31.0",
62
- "eslint-plugin-jest": "^27.9.0",
59
+ "eslint": "^9",
63
60
  "eslint-plugin-jsdoc": "^48.11.0",
64
- "eslint-plugin-n": "^15.7.0",
65
- "eslint-plugin-node": "^11.1.0",
66
- "eslint-plugin-promise": "^6.6.0",
61
+ "neostandard": "^0",
67
62
  "jest": "^29.5.0",
68
63
  "nock": "^13.2.9",
69
64
  "oclif": "^4.17.13",
70
65
  "stdout-stderr": "^0.1.9"
71
66
  },
72
67
  "engines": {
73
- "node": ">=18"
68
+ "node": ">=20"
74
69
  },
75
70
  "files": [
76
71
  "bin/run",
@@ -40,6 +40,31 @@ class BaseCommand extends Command {
40
40
 
41
41
  async init () {
42
42
  await super.init()
43
+ // Normalize hooks from plugins loaded by oclif v2 into this v4 Config.
44
+ // oclif v2 stores hooks as string arrays; v4 expects {identifier, target} objects.
45
+ // Only mutate when string hooks are present; guard against frozen plugin references.
46
+ const pluginList = typeof this.config.getPluginsList === 'function'
47
+ ? this.config.getPluginsList()
48
+ : [...(this.config.plugins?.values() ?? [])]
49
+ for (const plugin of pluginList) {
50
+ if (!plugin.hooks) {
51
+ continue
52
+ }
53
+ for (const [event, hooks] of Object.entries(plugin.hooks)) {
54
+ const hooksArr = Array.isArray(hooks) ? hooks : [hooks]
55
+ if (!hooksArr.some(h => typeof h === 'string')) {
56
+ continue
57
+ }
58
+ try {
59
+ plugin.hooks[event] = hooksArr.map(h =>
60
+ typeof h === 'string' ? { identifier: 'default', target: h } : h
61
+ )
62
+ } catch {
63
+ // plugin.hooks is frozen or sealed; skip normalization for this event
64
+ }
65
+ }
66
+ }
67
+
43
68
  // setup a prompt that outputs to stderr
44
69
  this.prompt = inquirer.createPromptModule({ output: process.stderr })
45
70
 
@@ -30,6 +30,7 @@ const LogForwarding = require('../../lib/log-forwarding')
30
30
  const { sendAppAssetsDeployedAuditLog, sendAppDeployAuditLog } = require('../../lib/audit-logger')
31
31
  const { setRuntimeApiHostAndAuthHandler, getAccessToken } = require('../../lib/auth-helper')
32
32
  const logActions = require('../../lib/log-actions')
33
+ const aioConfigLoader = require('@adobe/aio-lib-core-config')
33
34
 
34
35
  const PRE_DEPLOY_EVENT_REG = 'pre-deploy-event-reg'
35
36
  const POST_DEPLOY_EVENT_REG = 'post-deploy-event-reg'
@@ -230,6 +231,7 @@ class Deploy extends BuildCommand {
230
231
  // output should be "Error : <plugin-name> : <error-message>\n" for each failure
231
232
  this.error(hookResults.failures.map(f => `${f.plugin.name} : ${f.error.message}`).join('\nError: '), { exit: 1 })
232
233
  }
234
+ aioConfigLoader.reload()
233
235
  deployedRuntimeEntities = await rtLib.deployActions(config, { filterEntities, useForce: flags['force-deploy'] }, onProgress)
234
236
  }
235
237
 
@@ -192,6 +192,9 @@ class InitCommand extends TemplatesCommand {
192
192
  this.error(`Extension(s) '${notFound.join(', ')}' not found in the Template Registry.`)
193
193
  }
194
194
  return extensionTemplates.map(t => t.name)
195
+ } else if (flags.yes) {
196
+ // with --yes and no explicit template, default to standalone app (no prompts)
197
+ return []
195
198
  } else if (!flags['standalone-app']) {
196
199
  const noLogin = flags.import || !flags.login
197
200
  let [searchCriteria, orderByCriteria] = await this.getSearchCriteria(orgSupportedServices)
@@ -210,9 +213,12 @@ class InitCommand extends TemplatesCommand {
210
213
  }
211
214
  }
212
215
 
213
- async ensureDevTermAccepted (consoleCLI, orgId) {
216
+ async ensureDevTermAccepted (consoleCLI, orgId, skipPrompts = false) {
214
217
  const isTermAccepted = await consoleCLI.checkDevTermsForOrg(orgId)
215
218
  if (!isTermAccepted) {
219
+ if (skipPrompts) {
220
+ this.error('Developer Terms of Service have not been accepted for this organization. Please run `aio app init` without --yes to accept the terms first.')
221
+ }
216
222
  const terms = await consoleCLI.getDevTermsForOrg()
217
223
  const confirmDevTerms = await consoleCLI.prompt.promptConfirm(`${terms.text}
218
224
  \nYou have not accepted the Developer Terms of Service. Go to ${hyperlinker('https://www.adobe.com/go/developer-terms', 'https://www.adobe.com/go/developer-terms')} to view the terms. Do you accept the terms? (y/n):`)
@@ -294,26 +300,110 @@ class InitCommand extends TemplatesCommand {
294
300
 
295
301
  async selectConsoleOrg (consoleCLI, flags) {
296
302
  const organizations = await consoleCLI.getOrganizations()
297
- const selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org })
298
- await this.ensureDevTermAccepted(consoleCLI, selectedOrg.id)
303
+ if (!organizations || organizations.length === 0) {
304
+ this.error('No organizations found for the logged-in user')
305
+ }
306
+ // If --org was supplied, validate it against the full list regardless of how many orgs
307
+ // exist. This prevents silent mismatches when there is only one org but the caller
308
+ // passed a wrong id or code.
309
+ let selectedOrg
310
+ if (flags.org) {
311
+ selectedOrg = organizations.find(o => o.id === flags.org || o.code === flags.org)
312
+ if (!selectedOrg) {
313
+ this.error(`--org ${flags.org} not found`)
314
+ }
315
+ } else if (organizations.length > 1 && !flags.yes) {
316
+ // Multiple orgs and no --org: prompt interactively (only when not in --yes mode).
317
+ selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, {})
318
+ } else {
319
+ // Single org, or --yes with no --org: auto-select the first (and likely only) org.
320
+ selectedOrg = organizations[0]
321
+ this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`)
322
+ }
323
+ await this.ensureDevTermAccepted(consoleCLI, selectedOrg.id, flags.yes)
299
324
  return selectedOrg
300
325
  }
301
326
 
302
327
  async selectOrCreateConsoleProject (consoleCLI, org, flags) {
328
+ // Fetch all projects in the org upfront. This list is used both for uniqueness
329
+ // checks (--yes path) and for the interactive selection prompt (non-yes path).
303
330
  const projects = await consoleCLI.getProjects(org.id)
331
+
332
+ if (flags.yes) {
333
+ // Non-interactive path: no prompts are shown. Behavior depends on whether
334
+ // --project was explicitly supplied by the caller.
335
+
336
+ if (flags.project) {
337
+ // --project was supplied. Try to find it in the existing list by id or name.
338
+ // Matching by id supports callers who pass a project id rather than a name.
339
+ const existing = projects.find(p => p.id === flags.project || p.name === flags.project)
340
+ if (existing) {
341
+ // Project already exists — return it as-is. isNew is intentionally NOT set
342
+ // so downstream code knows not to treat this as a newly created project.
343
+ this.log(`Using existing project: '${existing.name}'`)
344
+ return existing
345
+ }
346
+ // Project does not exist — create it using the caller-supplied name directly.
347
+ // title and description are derived from the name since no other info is available.
348
+ this.log(`Project '${flags.project}' not found, creating it`)
349
+ const project = await consoleCLI.createProject(org.id, {
350
+ name: flags.project,
351
+ title: flags.project,
352
+ description: `App Builder Project ${flags.project} - generated by an agent`
353
+ })
354
+ project.isNew = true
355
+ return project
356
+ }
357
+
358
+ // No --project supplied. Auto-generate a unique name of the form "App{N}"
359
+ // where N is the lowest positive integer not already used by an existing project.
360
+ // This mimics sequential behaviour (App1, App2, ...) and fills
361
+ // gaps left by deleted projects (e.g. if App2 was deleted, it is reused
362
+ // before App4 is attempted).
363
+ const existingNames = new Set(projects.map(p => p.name))
364
+ const MAX_SUFFIX = 10000
365
+ let suffix = 1
366
+ while (existingNames.has(`App${suffix}`)) {
367
+ suffix++
368
+ if (suffix > MAX_SUFFIX) {
369
+ this.error(`Could not find an available generated App name after ${MAX_SUFFIX} attempts`)
370
+ }
371
+ }
372
+
373
+ const generatedName = `App${suffix}`
374
+ const generatedTitle = `App Builder Project ${suffix}`
375
+ const generatedDescription = `App Builder Project ${suffix} - generated`
376
+
377
+ this.log(`Auto-generating project name: '${generatedName}'`)
378
+ const project = await consoleCLI.createProject(org.id, {
379
+ name: generatedName,
380
+ title: generatedTitle,
381
+ description: generatedDescription
382
+ })
383
+ project.isNew = true
384
+ return project
385
+ }
386
+
387
+ // Interactive path: prompt the user to select an existing project or create a new one.
388
+ // If --project was supplied it is used to pre-populate the selection (by id or name)
389
+ // but the prompt is still shown so the user can confirm or change it.
304
390
  let project = await consoleCLI.promptForSelectProject(
305
391
  projects,
306
392
  { projectId: flags.project, projectName: flags.project },
307
393
  { allowCreate: true }
308
394
  )
309
395
  if (!project) {
396
+ // promptForSelectProject returns null when the user selects "Create new project" or
397
+ // escapes the prompt. If --project was explicitly provided but not found/selected,
398
+ // always error — never silently create a different project.
310
399
  if (flags.project) {
311
400
  this.error(`--project ${flags.project} not found`)
401
+ } else {
402
+ // User chose to create a new project — collect details interactively and create it.
403
+ const projectDetails = await consoleCLI.promptForCreateProjectDetails()
404
+ project = await consoleCLI.createProject(org.id, projectDetails)
405
+ project.isNew = true
312
406
  }
313
- // user has escaped project selection prompt, let's create a new one
314
- const projectDetails = await consoleCLI.promptForCreateProjectDetails()
315
- project = await consoleCLI.createProject(org.id, projectDetails)
316
- project.isNew = true
317
407
  }
318
408
  return project
319
409
  }
@@ -324,7 +414,7 @@ class InitCommand extends TemplatesCommand {
324
414
  const workspaces = await consoleCLI.getWorkspaces(org.id, project.id)
325
415
  let workspace = workspaces.find(w => w.name.toLowerCase() === workspaceName.toLowerCase())
326
416
  if (!workspace) {
327
- if (flags['confirm-new-workspace']) {
417
+ if (!flags.yes && flags['confirm-new-workspace']) {
328
418
  const shouldNewWorkspace = await consoleCLI.prompt.promptConfirm(`Workspace '${workspaceName}' does not exist \n > Do you wish to create a new workspace?`)
329
419
  if (!shouldNewWorkspace) {
330
420
  this.error(`Workspace '${workspaceName}' does not exist and creation aborted`)
@@ -22,7 +22,6 @@ const jsYaml = require('js-yaml')
22
22
  const { USER_CONFIG_FILE, DEPLOY_CONFIG_FILE, PACKAGE_LOCK_FILE } = require('../../lib/defaults')
23
23
  const ora = require('ora')
24
24
 
25
- // eslint-disable-next-line node/no-missing-require
26
25
  const libConfig = require('@adobe/aio-cli-lib-app-config')
27
26
 
28
27
  class InstallCommand extends BaseCommand {
@@ -22,7 +22,6 @@ const { getObjectValue } = require('../../lib/app-helper')
22
22
  const ora = require('ora')
23
23
  const junk = require('junk')
24
24
 
25
- // eslint-disable-next-line node/no-missing-require
26
25
  const libConfig = require('@adobe/aio-cli-lib-app-config')
27
26
 
28
27
  const DIST_FOLDER = 'dist'
@@ -40,11 +40,11 @@ class Cleanup {
40
40
  try {
41
41
  await this.run()
42
42
  aioLogger.info('exiting!')
43
- process.exit(0) // eslint-disable-line no-process-exit
43
+ process.exit(0)
44
44
  } catch (e) {
45
45
  aioLogger.error('unexpected error while cleaning up!')
46
46
  aioLogger.error(e)
47
- process.exit(1) // eslint-disable-line no-process-exit
47
+ process.exit(1)
48
48
  }
49
49
  })
50
50
  }
@@ -20,7 +20,6 @@ const ajvAddFormats = require('ajv-formats')
20
20
  * @returns {object} with keys valid (boolean) and errors (object). errors is null if no errors
21
21
  */
22
22
  function validateJsonWithSchema (fileJson, schemaName) {
23
- /* eslint-disable-next-line node/no-unpublished-require */
24
23
  const schemas = require('../../schema/index')
25
24
  const ajv = new Ajv({
26
25
  allErrors: true,
@@ -9,7 +9,6 @@ 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
- /* eslint-disable no-template-curly-in-string */
13
12
  const aioLogger = require('@adobe/aio-lib-core-logging')('@adobe/aio-cli-plugin-app:runDev', { provider: 'debug' })
14
13
  const rtLib = require('@adobe/aio-lib-runtime')
15
14
  const rtLibUtils = rtLib.utils