enable agentic workflows#912
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
Enables fully non-interactive aio app init --yes flows suitable for agents/CI by removing interactive prompts and auto-selecting/defaulting required initialization choices.
Changes:
- Default to standalone app (no template selection prompt) when
--yesis used without--template. - Add non-interactive org selection behavior, dev-terms handling, and workspace creation behavior under
--yes. - Expand test coverage for new
--yesbehaviors including project identifier generation and org/dev-terms edge cases.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/commands/app/init.js |
Implements non-interactive --yes behaviors for templates, org selection, dev terms, project creation, and workspace creation. |
test/commands/app/init.test.js |
Adds/updates tests to cover new --yes non-interactive paths and edge cases. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // initially select the first org, if multiple orgs are present, prompt user to select one | ||
| let selectedOrg = organizations[0] | ||
| if (organizations.length > 1) { | ||
| if (flags.yes) { | ||
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | ||
| } else { | ||
| selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org }) | ||
| } |
There was a problem hiding this comment.
When --yes is used and multiple organizations are returned, this logic always selects organizations[0] and skips promptForSelectOrganization, which means an explicit --org value is ignored. For non-interactive/CI usage, --org should still be honored (auto-select the matching org if present, otherwise error) to avoid initializing against an unintended organization.
| // initially select the first org, if multiple orgs are present, prompt user to select one | |
| let selectedOrg = organizations[0] | |
| if (organizations.length > 1) { | |
| if (flags.yes) { | |
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | |
| } else { | |
| selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org }) | |
| } | |
| let selectedOrg | |
| if (flags.org) { | |
| selectedOrg = organizations.find(org => | |
| org.id === flags.org || | |
| org.orgId === flags.org || | |
| org.code === flags.org || | |
| org.orgCode === flags.org || | |
| org.name === flags.org | |
| ) | |
| if (!selectedOrg) { | |
| this.error(`Organization '${flags.org}' not found for the logged-in user`) | |
| } | |
| if (flags.yes) { | |
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | |
| } | |
| } else { | |
| // initially select the first org, if multiple orgs are present, prompt user to select one | |
| selectedOrg = organizations[0] | |
| if (organizations.length > 1) { | |
| if (flags.yes) { | |
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | |
| } else { | |
| selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org }) | |
| } | |
| } |
| if (flags.yes) { | ||
| // Use the aio-lib-console SDK to fetch a pre-generated unique project name (IOC-7430) | ||
| // Returns { name: '280TomatoGull', title: 'Project 289' } | ||
| let generatedName | ||
| let generatedTitle | ||
| try { | ||
| const data = await consoleCLI.getProjectNextAvailableIdentifiers(org.id) | ||
| generatedName = data.name || data.title.replace(/\s+/g, '') | ||
| generatedTitle = data.title || generatedName | ||
| aioLogger.debug(`next-available-identifiers response: ${JSON.stringify(data)}`) | ||
| } catch (e) { | ||
| aioLogger.debug(`Failed to fetch next-available-identifiers, falling back to timestamp name: ${e.message}`) | ||
| generatedName = `app${Date.now()}` | ||
| generatedTitle = generatedName | ||
| } | ||
| this.log(`Auto-generating project name: '${generatedName}'`) | ||
| const project = await consoleCLI.createProject(org.id, { |
There was a problem hiding this comment.
In the --yes path you always create a new Console project, ignoring --project even if the user provided it. This can create unexpected projects in CI; consider: if --project is provided, resolve it from getProjects() and use it (or error if not found) instead of auto-creating.
| async selectOrCreateConsoleProject (consoleCLI, org, flags) { | ||
| const projects = await consoleCLI.getProjects(org.id) | ||
|
|
||
| if (flags.yes) { |
There was a problem hiding this comment.
getProjects(org.id) is executed before the --yes early-return branch but its result is unused when flags.yes is true, adding an avoidable network/API call to non-interactive runs. Consider moving the getProjects call into the non---yes branch.
shazron
left a comment
There was a problem hiding this comment.
I'll leave it to you to resolve the Copilot comments; also, add the PRs this PR depends on before it can get merged
There was a problem hiding this comment.
🤖 PR Reviewer
The diff adds a --yes flag for non-interactive app initialization, auto-selecting org/project/workspace and skipping prompts. The implementation is generally clean and well-tested, but there are a few logic issues and edge cases worth addressing.
📝 4 suggestion(s) - Please review inline comments below.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
| if (organizations.length > 1) { | ||
| if (flags.yes) { | ||
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | ||
| } else { |
There was a problem hiding this comment.
When flags.yes is true and there are multiple orgs, the code auto-selects organizations[0] (the first org). However, if flags.org is also provided, it should try to match the specified org rather than blindly picking the first one. This silently ignores --org flag when --yes is set with multiple orgs.
| } else { | |
| if (flags.yes) { | |
| if (flags.org) { | |
| const matched = organizations.find(o => o.id === flags.org || o.code === flags.org) | |
| if (matched) selectedOrg = matched | |
| } | |
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | |
| } else { | |
| selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org }) | |
| } |
| try { | ||
| const data = await consoleCLI.getProjectNextAvailableIdentifiers(org.id) | ||
| generatedName = data.name || data.title.replace(/\s+/g, '') | ||
| generatedTitle = data.title || generatedName |
There was a problem hiding this comment.
The fallback name uses Date.now() which returns milliseconds since epoch (e.g., app1718000000000) — 16 chars. Project names may have length constraints. Consider a shorter fallback like a truncated timestamp or random suffix.
| generatedTitle = data.title || generatedName | |
| generatedName = `app${Date.now().toString(36)}` | |
| generatedTitle = generatedName |
| let project = await consoleCLI.promptForSelectProject( | ||
| projects, | ||
| { projectId: flags.project, projectName: flags.project }, | ||
| { allowCreate: true } |
There was a problem hiding this comment.
The condition if (flags.project && !flags.yes) combined with the else branch means: if flags.project is set AND flags.yes is true, it falls into the else branch and calls promptForCreateProjectDetails interactively — defeating the purpose of --yes. The intent should be: error if project not found regardless of --yes, or auto-create. The original code errored when flags.project was set; this change silently ignores a user-specified project that wasn't found.
| { allowCreate: true } | |
| if (flags.project) { | |
| this.error(`--project ${flags.project} not found`) | |
| } else { | |
| // user has escaped project selection prompt, let's create a new one | |
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | |
| project = await consoleCLI.createProject(org.id, projectDetails) | |
| project.isNew = true | |
| } |
| let workspace = workspaces.find(w => w.name.toLowerCase() === workspaceName.toLowerCase()) | ||
| if (!workspace) { | ||
| if (flags['confirm-new-workspace']) { | ||
| if (!flags.yes && flags['confirm-new-workspace']) { |
There was a problem hiding this comment.
When flags.yes is true and a workspace doesn't exist, it silently auto-creates it. But if the user did NOT pass --confirm-new-workspace and also did NOT pass --yes, the original code would also skip the prompt and auto-create (only prompts when confirm-new-workspace is set). The condition !flags.yes && flags['confirm-new-workspace'] means: only prompt when NOT yes AND confirm-new-workspace is set. This looks correct, but it's worth verifying the else branch (auto-create) is safe when yes=false and confirm-new-workspace=false — which is the existing behavior preserved here.
| if (!flags.yes && flags['confirm-new-workspace']) { | |
| if (!flags.yes && flags['confirm-new-workspace']) { |
There was a problem hiding this comment.
🤖 PR Reviewer
The diff addresses several previously raised concerns and adds meaningful non-interactive (--yes) support with good test coverage. The re-raised issue about --yes ignoring --org when multiple orgs exist is still present. The condition if (flags.project && !flags.yes) in the interactive path is dead code since that branch is only reached when flags.yes is false, but it introduces a subtle bug: if flags.project is set, was not found by promptForSelectProject, and flags.yes is false, the code now silently creates a new project instead of erroring — which is a regression from the original behavior and matches the previously raised suggestion to always error when --project is provided but not found.
🔄 2 re-raised suggestion(s) from previous review
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
| const projects = await consoleCLI.getProjects(org.id) | ||
|
|
||
| if (flags.yes) { | ||
| // Non-interactive path: no prompts are shown. Behavior depends on whether |
There was a problem hiding this comment.
[Re-raised] When flags.yes is true and there are multiple orgs, the code auto-selects organizations[0] (the first org) and ignores the --org flag entirely. If --org is also provided it should try to match the specified org rather than blindly picking the first one.
| // Non-interactive path: no prompts are shown. Behavior depends on whether | |
| if (organizations.length > 1) { | |
| if (flags.yes) { | |
| if (flags.org) { | |
| const matched = organizations.find(o => o.id === flags.org || o.code === flags.org) | |
| if (matched) selectedOrg = matched | |
| } | |
| this.log(`Auto-selecting organization: '${selectedOrg.name || selectedOrg.id}'`) | |
| } else { | |
| selectedOrg = await consoleCLI.promptForSelectOrganization(organizations, { orgId: flags.org, orgCode: flags.org }) | |
| } | |
| } |
| } else { | ||
| // User chose to create a new project — collect details interactively and create it. | ||
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | ||
| project = await consoleCLI.createProject(org.id, projectDetails) | ||
| project.isNew = true | ||
| } | ||
| // user has escaped project selection prompt, let's create a new one | ||
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | ||
| project = await consoleCLI.createProject(org.id, projectDetails) | ||
| project.isNew = true | ||
| } | ||
| return project | ||
| } |
There was a problem hiding this comment.
[Re-raised] The condition if (flags.project && !flags.yes) is dead code in this branch because flags.yes is always false here (the flags.yes path returns early above). More importantly, this is a regression: if flags.project was provided but promptForSelectProject returned null (project not found), the original code errored. Now it silently falls into the else and creates a new project with interactively collected details, ignoring the user's explicit --project value.
| } else { | |
| // User chose to create a new project — collect details interactively and create it. | |
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | |
| project = await consoleCLI.createProject(org.id, projectDetails) | |
| project.isNew = true | |
| } | |
| // user has escaped project selection prompt, let's create a new one | |
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | |
| project = await consoleCLI.createProject(org.id, projectDetails) | |
| project.isNew = true | |
| } | |
| return project | |
| } | |
| if (flags.project) { | |
| this.error(`--project ${flags.project} not found`) | |
| } else { | |
| // User chose to create a new project — collect details interactively and create it. | |
| const projectDetails = await consoleCLI.promptForCreateProjectDetails() | |
| project = await consoleCLI.createProject(org.id, projectDetails) | |
| project.isNew = true | |
| } |
There was a problem hiding this comment.
🤖 PR Reviewer
The diff addresses both previously raised issues and adds substantial new functionality for the --yes flag (non-interactive mode). The org matching, dev terms enforcement, project auto-generation, and workspace creation logic are all well-implemented with good test coverage. The code is clear and the comments are helpful. One minor issue remains: when a single org exists, the --org flag mismatch is silently ignored (no validation performed), which could confuse callers who expect an error if their explicit --org value doesn't match the only available org.
✅ LGTM! This PR looks good to merge.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
| this.error(`--org ${flags.org} not found`) | ||
| } | ||
| selectedOrg = found | ||
| } |
There was a problem hiding this comment.
When only one organization exists (organizations.length === 1), the --org flag is never validated against the single org's id or code. A caller who passes --org wrong-id will silently proceed with the only org instead of getting an informative error. Consider validating --org even in the single-org case.
| } | |
| let selectedOrg = organizations[0] | |
| if (flags.org && selectedOrg.id !== flags.org && selectedOrg.code !== flags.org) { | |
| // Single org present but it doesn't match the explicitly supplied --org value. | |
| // This is almost certainly a misconfiguration — error early. | |
| if (organizations.length === 1) { | |
| this.error(`--org ${flags.org} not found`) | |
| } | |
| } |
There was a problem hiding this comment.
🤖 PR Reviewer
The diff addresses the previously raised re-review suggestion by validating --org against the full organization list regardless of list size, and adds comprehensive non-interactive (--yes) support with proper error handling and test coverage. The implementation is clean, well-commented, and the test suite thoroughly covers the new paths. No significant issues remain.
✅ LGTM! This PR looks good to merge.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
There was a problem hiding this comment.
🤖 PR Reviewer
The diff introduces a well-structured --yes (non-interactive) mode with reasonable defaults for org/project selection, dev-terms handling, and workspace creation. The logic is clear and well-commented, and test coverage for the new paths is thorough. A few minor issues exist: the auto-select log message fires for the wrong case (single-org without --yes), an unbounded while loop could hang on very large project lists, and the unused mock added to the test file is a minor nit.
📝 3 suggestion(s) - Please review inline comments below.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
There was a problem hiding this comment.
🤖 PR Reviewer
The diff introduces a well-structured non-interactive (--yes) path with auto-org selection, auto-project naming, dev-terms guard, and workspace creation bypass. Test coverage is thorough. There are two concrete issues: a syntax error (duplicate/missing closing brace in selectConsoleOrg leaves the method unclosed and the ensureDevTermAccepted call is dropped), and the previously raised auto-select log condition is still present in the broken block.
🔄 1 re-raised suggestion(s) from previous review
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
There was a problem hiding this comment.
🤖 PR Reviewer
The diff is well-structured, addresses the previously re-raised syntax issue, and introduces meaningful non-interactive (--yes) support with solid test coverage. The logic is clear and handles edge cases (missing org, unaccepted terms, name collisions). One minor concern is that the --yes multi-org auto-selection silently picks the first org rather than requiring --org, which could be surprising in production environments, but this appears intentional. No blocking issues found.
✅ LGTM! This PR looks good to merge.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
There was a problem hiding this comment.
🤖 PR Reviewer
The diff introduces a well-structured non-interactive (--yes) mode for app init, covering org selection, project creation, workspace creation, template selection, and dev terms handling. The logic is clear, well-commented, and thoroughly tested. A few minor issues exist around error handling robustness and a potential off-by-one in the MAX_SUFFIX loop.
📝 4 suggestion(s) - Please review inline comments below.
💡 How to re-trigger
Comment /review or /pr-reviewer on this PR
⚠️ Inline comments could not be attached (lines not in diff). See summary above.
Description
feat: support fully non-interactive
aio app init --yesfor agent/CI usageThis PR enables
aio app init -yto run completely without user prompts, making it safe to call from agents and CI pipelines.Changes
aio-cli-plugin-app--yesis passed — calls the newgetProjectNextAvailableIdentifiersAPI to get a unique color-animal name (e.g.TomatoGull,Project 289). Falls back toapp{timestamp}if the API is unavailable.--yesand multiple orgs exist, the first org is selected automatically and logged.--yessurfaces a clear actionable error rather than hanging on an interactive prompt.--yesand no--template— skips interactive template selection entirely.--yesbypasses theconfirm-new-workspaceprompt and creates immediately.selectConsoleOrgnow errors with a descriptive message ifgetOrganizationsreturns nothing.aio-lib-consolegetProjectNextAvailableIdentifiers(organizationId, includeName = true)insrc/index.js— wraps the newGET /console/organizations/{orgId}/projects/next-available-identifiers?includeName=trueendpoint (IOC-7430). Returns{ name, title }.spec/patch/get_project_next_available_identifiers.jsonand rannpm run patch-spec.aio-cli-lib-consolegetProjectNextAvailableIdentifiers(orgId)inlib/index.js— wrapper method onLibConsoleCLIfollowing the same spinner/logger pattern asgetProjects.Tests
src/commands/app/init.js--yesnon-interactive paths, null org guard, fallback name generation, and allname/titleresponse combinations from the identifiers API.Related Issue
Motivation and Context
How Has This Been Tested?
Screenshots (if appropriate):
Types of changes
Checklist: