@hyperdrive.bot/cli 1.0.2
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/README.md +1598 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +3 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/account/add.d.ts +16 -0
- package/dist/commands/account/add.js +185 -0
- package/dist/commands/account/list.d.ts +6 -0
- package/dist/commands/account/list.js +37 -0
- package/dist/commands/account/remove.d.ts +11 -0
- package/dist/commands/account/remove.js +57 -0
- package/dist/commands/auth/login.d.ts +16 -0
- package/dist/commands/auth/login.js +178 -0
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +39 -0
- package/dist/commands/auth/refresh.d.ts +6 -0
- package/dist/commands/auth/refresh.js +66 -0
- package/dist/commands/auth/status.d.ts +6 -0
- package/dist/commands/auth/status.js +63 -0
- package/dist/commands/ci/account/create.d.ts +16 -0
- package/dist/commands/ci/account/create.js +158 -0
- package/dist/commands/ci/account/delete.d.ts +14 -0
- package/dist/commands/ci/account/delete.js +88 -0
- package/dist/commands/ci/account/list.d.ts +10 -0
- package/dist/commands/ci/account/list.js +65 -0
- package/dist/commands/config/get.d.ts +9 -0
- package/dist/commands/config/get.js +37 -0
- package/dist/commands/config/set.d.ts +10 -0
- package/dist/commands/config/set.js +48 -0
- package/dist/commands/config/show.d.ts +6 -0
- package/dist/commands/config/show.js +10 -0
- package/dist/commands/deployment/create.d.ts +30 -0
- package/dist/commands/deployment/create.js +188 -0
- package/dist/commands/deployment/get.d.ts +13 -0
- package/dist/commands/deployment/get.js +101 -0
- package/dist/commands/deployment/launch.d.ts +15 -0
- package/dist/commands/deployment/launch.js +105 -0
- package/dist/commands/deployment/list.d.ts +11 -0
- package/dist/commands/deployment/list.js +91 -0
- package/dist/commands/domain/current.d.ts +6 -0
- package/dist/commands/domain/current.js +18 -0
- package/dist/commands/domain/list.d.ts +6 -0
- package/dist/commands/domain/list.js +42 -0
- package/dist/commands/domain/switch.d.ts +9 -0
- package/dist/commands/domain/switch.js +40 -0
- package/dist/commands/example.d.ts +13 -0
- package/dist/commands/example.js +24 -0
- package/dist/commands/git/connect.d.ts +10 -0
- package/dist/commands/git/connect.js +56 -0
- package/dist/commands/git/disconnect.d.ts +11 -0
- package/dist/commands/git/disconnect.js +93 -0
- package/dist/commands/git/list.d.ts +10 -0
- package/dist/commands/git/list.js +53 -0
- package/dist/commands/git/sync.d.ts +18 -0
- package/dist/commands/git/sync.js +235 -0
- package/dist/commands/init.d.ts +188 -0
- package/dist/commands/init.js +817 -0
- package/dist/commands/jira/connect.d.ts +9 -0
- package/dist/commands/jira/connect.js +141 -0
- package/dist/commands/jira/status.d.ts +9 -0
- package/dist/commands/jira/status.js +118 -0
- package/dist/commands/module/analyze.d.ts +29 -0
- package/dist/commands/module/analyze.js +201 -0
- package/dist/commands/module/create.d.ts +42 -0
- package/dist/commands/module/create.js +498 -0
- package/dist/commands/module/destroy.d.ts +11 -0
- package/dist/commands/module/destroy.js +77 -0
- package/dist/commands/module/get.d.ts +10 -0
- package/dist/commands/module/get.js +43 -0
- package/dist/commands/module/link.d.ts +15 -0
- package/dist/commands/module/link.js +175 -0
- package/dist/commands/module/list.d.ts +9 -0
- package/dist/commands/module/list.js +51 -0
- package/dist/commands/module/reanalyze.d.ts +30 -0
- package/dist/commands/module/reanalyze.js +206 -0
- package/dist/commands/module/update.d.ts +27 -0
- package/dist/commands/module/update.js +102 -0
- package/dist/commands/parameter/add.d.ts +15 -0
- package/dist/commands/parameter/add.js +99 -0
- package/dist/commands/parameter/backfill.d.ts +12 -0
- package/dist/commands/parameter/backfill.js +113 -0
- package/dist/commands/parameter/clear.d.ts +14 -0
- package/dist/commands/parameter/clear.js +95 -0
- package/dist/commands/parameter/list.d.ts +14 -0
- package/dist/commands/parameter/list.js +92 -0
- package/dist/commands/parameter/pull.d.ts +14 -0
- package/dist/commands/parameter/pull.js +124 -0
- package/dist/commands/parameter/remove.d.ts +15 -0
- package/dist/commands/parameter/remove.js +90 -0
- package/dist/commands/parameter/sync.d.ts +14 -0
- package/dist/commands/parameter/sync.js +153 -0
- package/dist/commands/parameter/update.d.ts +15 -0
- package/dist/commands/parameter/update.js +100 -0
- package/dist/commands/stage/create.d.ts +28 -0
- package/dist/commands/stage/create.js +312 -0
- package/dist/commands/stage/list.d.ts +9 -0
- package/dist/commands/stage/list.js +63 -0
- package/dist/commands/test-api.d.ts +9 -0
- package/dist/commands/test-api.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/auth-service.d.ts +84 -0
- package/dist/services/auth-service.js +240 -0
- package/dist/services/git.d.ts +46 -0
- package/dist/services/git.js +409 -0
- package/dist/services/hyperdrive-sigv4.d.ts +449 -0
- package/dist/services/hyperdrive-sigv4.js +375 -0
- package/dist/services/hyperdrive.d.ts +87 -0
- package/dist/services/hyperdrive.js +108 -0
- package/dist/services/log-tailer.d.ts +95 -0
- package/dist/services/log-tailer.js +242 -0
- package/dist/services/tenant-service.d.ts +106 -0
- package/dist/services/tenant-service.js +332 -0
- package/dist/utils/account-flow.d.ts +74 -0
- package/dist/utils/account-flow.js +228 -0
- package/dist/utils/auth-flow.d.ts +146 -0
- package/dist/utils/auth-flow.js +477 -0
- package/dist/utils/git-flow.d.ts +72 -0
- package/dist/utils/git-flow.js +232 -0
- package/dist/utils/jira-flow.d.ts +71 -0
- package/dist/utils/jira-flow.js +120 -0
- package/dist/utils/summary-display.d.ts +59 -0
- package/dist/utils/summary-display.js +140 -0
- package/dist/utils/validation.d.ts +15 -0
- package/dist/utils/validation.js +32 -0
- package/oclif.manifest.json +2819 -0
- package/package.json +112 -0
|
@@ -0,0 +1,817 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { TenantService } from '../services/tenant-service.js';
|
|
6
|
+
import { executeAccountAdd as executeAccountAddOriginal, openCloudFormationUrl, promptAccountDetails, registerAccount, waitForRoleVerification, } from '../utils/account-flow.js';
|
|
7
|
+
import { executeAuthFlow as executeAuthFlowOriginal } from '../utils/auth-flow.js';
|
|
8
|
+
import { executeGitConnect as executeGitConnectOriginal, promptGitProvider, } from '../utils/git-flow.js';
|
|
9
|
+
import { executeJiraConnect as executeJiraConnectOriginal, promptJiraConnect, promptJiraDomain, registerJiraDomain, } from '../utils/jira-flow.js';
|
|
10
|
+
import { displaySetupSummary } from '../utils/summary-display.js';
|
|
11
|
+
import { validateTenantDomain } from '../utils/validation.js';
|
|
12
|
+
// Module-level auth flow function - can be replaced for testing
|
|
13
|
+
let authFlowImpl = executeAuthFlowOriginal;
|
|
14
|
+
// Module-level account add flow function - can be replaced for testing
|
|
15
|
+
let accountAddImpl = executeAccountAddOriginal;
|
|
16
|
+
// Module-level git connect flow function - can be replaced for testing
|
|
17
|
+
let gitConnectImpl = executeGitConnectOriginal;
|
|
18
|
+
// Module-level jira connect flow function - can be replaced for testing
|
|
19
|
+
let jiraConnectImpl = executeJiraConnectOriginal;
|
|
20
|
+
/**
|
|
21
|
+
* Set a custom auth flow implementation (for testing)
|
|
22
|
+
*/
|
|
23
|
+
export function setAuthFlowImpl(impl) {
|
|
24
|
+
authFlowImpl = impl;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reset auth flow to original implementation
|
|
28
|
+
*/
|
|
29
|
+
export function resetAuthFlowImpl() {
|
|
30
|
+
authFlowImpl = executeAuthFlowOriginal;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Get the current auth flow implementation
|
|
34
|
+
*/
|
|
35
|
+
export function getAuthFlowImpl() {
|
|
36
|
+
return authFlowImpl;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Set a custom account add flow implementation (for testing)
|
|
40
|
+
*/
|
|
41
|
+
export function setAccountAddImpl(impl) {
|
|
42
|
+
accountAddImpl = impl;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Reset account add flow to original implementation
|
|
46
|
+
*/
|
|
47
|
+
export function resetAccountAddImpl() {
|
|
48
|
+
accountAddImpl = executeAccountAddOriginal;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Get the current account add flow implementation
|
|
52
|
+
*/
|
|
53
|
+
export function getAccountAddImpl() {
|
|
54
|
+
return accountAddImpl;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Set a custom git connect flow implementation (for testing)
|
|
58
|
+
*/
|
|
59
|
+
export function setGitConnectImpl(impl) {
|
|
60
|
+
gitConnectImpl = impl;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Reset git connect flow to original implementation
|
|
64
|
+
*/
|
|
65
|
+
export function resetGitConnectImpl() {
|
|
66
|
+
gitConnectImpl = executeGitConnectOriginal;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get the current git connect flow implementation
|
|
70
|
+
*/
|
|
71
|
+
export function getGitConnectImpl() {
|
|
72
|
+
return gitConnectImpl;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Set a custom jira connect flow implementation (for testing)
|
|
76
|
+
*/
|
|
77
|
+
export function setJiraConnectImpl(impl) {
|
|
78
|
+
jiraConnectImpl = impl;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Reset jira connect flow to original implementation
|
|
82
|
+
*/
|
|
83
|
+
export function resetJiraConnectImpl() {
|
|
84
|
+
jiraConnectImpl = executeJiraConnectOriginal;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the current jira connect flow implementation
|
|
88
|
+
*/
|
|
89
|
+
export function getJiraConnectImpl() {
|
|
90
|
+
return jiraConnectImpl;
|
|
91
|
+
}
|
|
92
|
+
export default class Init extends Command {
|
|
93
|
+
static description = 'Initialize Hyperdrive CLI with guided setup wizard';
|
|
94
|
+
static examples = [
|
|
95
|
+
'<%= config.bin %> <%= command.id %>',
|
|
96
|
+
];
|
|
97
|
+
accountsSkipped = false;
|
|
98
|
+
authSkipped = false;
|
|
99
|
+
connectedAccounts = [];
|
|
100
|
+
connectedGitProvider;
|
|
101
|
+
connectedJira;
|
|
102
|
+
gitSkipped = false;
|
|
103
|
+
jiraSkipped = false;
|
|
104
|
+
tenantService;
|
|
105
|
+
constructor(argv, config) {
|
|
106
|
+
super(argv, config);
|
|
107
|
+
this.tenantService = new TenantService();
|
|
108
|
+
}
|
|
109
|
+
async run() {
|
|
110
|
+
this.displayWelcome();
|
|
111
|
+
const shouldContinue = await this.promptContinue();
|
|
112
|
+
if (!shouldContinue) {
|
|
113
|
+
this.log('');
|
|
114
|
+
this.log(chalk.yellow('👋 Setup cancelled. Run `hd init` when you\'re ready to configure Hyperdrive.'));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
// Step 1: Configure tenant domain
|
|
118
|
+
this.log('');
|
|
119
|
+
const tenantDomain = await this.configureTenantDomain();
|
|
120
|
+
// Step 2: Authenticate user
|
|
121
|
+
this.log('');
|
|
122
|
+
await this.authenticateUser(tenantDomain);
|
|
123
|
+
// Step 3: AWS Account Setup (only if authenticated or skipped)
|
|
124
|
+
this.log('');
|
|
125
|
+
await this.addAwsAccounts(tenantDomain);
|
|
126
|
+
// Step 4: Git Provider Setup
|
|
127
|
+
this.log('');
|
|
128
|
+
await this.connectGitProvider();
|
|
129
|
+
// Step 5: Jira Integration Setup
|
|
130
|
+
this.log('');
|
|
131
|
+
await this.connectJira();
|
|
132
|
+
// Display summary
|
|
133
|
+
this.showSummary(tenantDomain);
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Add AWS accounts step in the init wizard
|
|
137
|
+
*
|
|
138
|
+
* Implements:
|
|
139
|
+
* - Initial prompt to add an AWS account
|
|
140
|
+
* - Loop to add multiple accounts
|
|
141
|
+
* - Skip option with helpful message
|
|
142
|
+
* - Summary display of connected accounts
|
|
143
|
+
*/
|
|
144
|
+
async addAwsAccounts(tenantDomain) {
|
|
145
|
+
this.log(chalk.blue('AWS Account Setup'));
|
|
146
|
+
this.log(chalk.dim('Connect AWS accounts that Hyperdrive can deploy to'));
|
|
147
|
+
this.log('');
|
|
148
|
+
// Check if authentication was skipped - warn user but allow to try
|
|
149
|
+
if (this.authSkipped) {
|
|
150
|
+
this.log(chalk.yellow('⚠') + ' Authentication was skipped. Account setup requires authentication.');
|
|
151
|
+
const { tryAnyway } = await inquirer.prompt([{
|
|
152
|
+
default: false,
|
|
153
|
+
message: 'Would you like to try adding an AWS account anyway?',
|
|
154
|
+
name: 'tryAnyway',
|
|
155
|
+
type: 'confirm',
|
|
156
|
+
}]);
|
|
157
|
+
if (!tryAnyway) {
|
|
158
|
+
this.accountsSkipped = true;
|
|
159
|
+
this.log(chalk.yellow('⚠') + " Skipped AWS account setup. Run 'hd account add' later.");
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
// Initial prompt to add an account
|
|
164
|
+
const { addAccount } = await inquirer.prompt([{
|
|
165
|
+
default: true,
|
|
166
|
+
message: 'Would you like to add an AWS account?',
|
|
167
|
+
name: 'addAccount',
|
|
168
|
+
type: 'confirm',
|
|
169
|
+
}]);
|
|
170
|
+
if (!addAccount) {
|
|
171
|
+
this.accountsSkipped = true;
|
|
172
|
+
this.log(chalk.yellow('⚠') + " Skipped AWS account setup. Run 'hd account add' later.");
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
// Loop to add multiple accounts
|
|
176
|
+
let continueAdding = true;
|
|
177
|
+
while (continueAdding) {
|
|
178
|
+
const result = await this.addSingleAwsAccount(tenantDomain);
|
|
179
|
+
if (result.success && result.accountId) {
|
|
180
|
+
this.connectedAccounts.push({
|
|
181
|
+
accountId: result.accountId,
|
|
182
|
+
alias: result.alias,
|
|
183
|
+
});
|
|
184
|
+
// Prompt to add another
|
|
185
|
+
const { addAnother } = await inquirer.prompt([{
|
|
186
|
+
default: false,
|
|
187
|
+
message: 'Add another AWS account?',
|
|
188
|
+
name: 'addAnother',
|
|
189
|
+
type: 'confirm',
|
|
190
|
+
}]);
|
|
191
|
+
continueAdding = addAnother;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
// Account add failed - offer retry or skip
|
|
195
|
+
const { action } = await inquirer.prompt([{
|
|
196
|
+
choices: [
|
|
197
|
+
{ name: 'Retry adding this account', value: 'retry' },
|
|
198
|
+
{ name: 'Skip this account and continue', value: 'skip' },
|
|
199
|
+
{ name: 'Skip AWS account setup entirely', value: 'skip_all' },
|
|
200
|
+
],
|
|
201
|
+
message: 'Account setup failed. What would you like to do?',
|
|
202
|
+
name: 'action',
|
|
203
|
+
type: 'list',
|
|
204
|
+
}]);
|
|
205
|
+
if (action === 'retry') {
|
|
206
|
+
// Continue the loop to retry
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
else if (action === 'skip') {
|
|
210
|
+
// Ask if they want to add a different account
|
|
211
|
+
const { addAnother } = await inquirer.prompt([{
|
|
212
|
+
default: false,
|
|
213
|
+
message: 'Would you like to add a different AWS account?',
|
|
214
|
+
name: 'addAnother',
|
|
215
|
+
type: 'confirm',
|
|
216
|
+
}]);
|
|
217
|
+
continueAdding = addAnother;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// Skip all
|
|
221
|
+
continueAdding = false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Display summary
|
|
226
|
+
this.displayAccountSummary();
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Add a single AWS account with proper separation of concerns
|
|
230
|
+
*
|
|
231
|
+
* Architecture:
|
|
232
|
+
* 1. Prompt for account details (no spinner - user needs to see prompts)
|
|
233
|
+
* 2. Check for duplicate account ID
|
|
234
|
+
* 3. Register account with API (with spinner - actual work happening)
|
|
235
|
+
* 4. Handle role creation if needed
|
|
236
|
+
*/
|
|
237
|
+
async addSingleAwsAccount(tenantDomain) {
|
|
238
|
+
try {
|
|
239
|
+
// Step 1: Collect account details from user (NO SPINNER - user needs to see prompts!)
|
|
240
|
+
const accountData = await promptAccountDetails();
|
|
241
|
+
// Step 2: Check for duplicate account ID
|
|
242
|
+
const isDuplicate = this.connectedAccounts.some(account => account.accountId === accountData.accountId);
|
|
243
|
+
if (isDuplicate) {
|
|
244
|
+
this.log(chalk.yellow('⚠') + ` Account ${accountData.accountId} is already connected`);
|
|
245
|
+
this.log(chalk.gray(' Skipping duplicate account...'));
|
|
246
|
+
return { success: false };
|
|
247
|
+
}
|
|
248
|
+
// Step 3: Register account with API (WITH SPINNER - actual API work)
|
|
249
|
+
const spinner = ora('Registering AWS account...').start();
|
|
250
|
+
const result = await registerAccount(accountData);
|
|
251
|
+
if (!result.success) {
|
|
252
|
+
spinner.fail('Failed to register AWS account');
|
|
253
|
+
this.log(chalk.red('✗') + ` Error: ${result.error || 'Unknown error'}`);
|
|
254
|
+
return { success: false };
|
|
255
|
+
}
|
|
256
|
+
spinner.succeed('AWS account registered');
|
|
257
|
+
this.log(chalk.green('✓') + ` Account ${result.accountId} added${result.accountAlias ? ` (${result.accountAlias})` : ''}`);
|
|
258
|
+
// Step 3: Handle role creation if needed
|
|
259
|
+
if (result.quickCreateUrl) {
|
|
260
|
+
await this.handleRoleCreation(result);
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
accountId: result.accountId,
|
|
264
|
+
alias: result.accountAlias,
|
|
265
|
+
success: true,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
270
|
+
this.log(chalk.red('✗') + ` Error: ${errorMessage}`);
|
|
271
|
+
return { success: false };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Authenticate user using OAuth PKCE flow with retry logic
|
|
276
|
+
*
|
|
277
|
+
* Implements:
|
|
278
|
+
* - Spinner display during authentication
|
|
279
|
+
* - Retry logic (max 3 attempts)
|
|
280
|
+
* - Skip option after failed attempts
|
|
281
|
+
*/
|
|
282
|
+
async authenticateUser(tenantDomain) {
|
|
283
|
+
const MAX_RETRIES = 3;
|
|
284
|
+
let attempts = 0;
|
|
285
|
+
this.log(chalk.blue('Authenticating with Hyperdrive...'));
|
|
286
|
+
while (attempts < MAX_RETRIES) {
|
|
287
|
+
attempts++;
|
|
288
|
+
const spinner = ora('Opening browser for authentication...').start();
|
|
289
|
+
try {
|
|
290
|
+
const result = await getAuthFlowImpl()({
|
|
291
|
+
logger: (message) => this.log(message),
|
|
292
|
+
tenantDomain
|
|
293
|
+
});
|
|
294
|
+
if (result.success) {
|
|
295
|
+
spinner.succeed('Browser authentication complete');
|
|
296
|
+
this.log(chalk.green('✓') + ' Authentication successful');
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
spinner.fail('Authentication failed');
|
|
301
|
+
this.log(chalk.red('✗') + ` Authentication failed: ${result.error || 'Unknown error'}`);
|
|
302
|
+
// Check if we should retry or offer skip
|
|
303
|
+
if (attempts < MAX_RETRIES) {
|
|
304
|
+
const { retry } = await inquirer.prompt([
|
|
305
|
+
{
|
|
306
|
+
default: true,
|
|
307
|
+
message: 'Authentication failed. Would you like to retry?',
|
|
308
|
+
name: 'retry',
|
|
309
|
+
type: 'confirm',
|
|
310
|
+
},
|
|
311
|
+
]);
|
|
312
|
+
if (!retry) {
|
|
313
|
+
// User chose not to retry, offer skip
|
|
314
|
+
await this.handleAuthSkip();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
// Max retries reached, offer skip
|
|
320
|
+
const { skip } = await inquirer.prompt([
|
|
321
|
+
{
|
|
322
|
+
default: false,
|
|
323
|
+
message: 'Authentication failed 3 times. Skip and continue?',
|
|
324
|
+
name: 'skip',
|
|
325
|
+
type: 'confirm',
|
|
326
|
+
},
|
|
327
|
+
]);
|
|
328
|
+
if (skip) {
|
|
329
|
+
await this.handleAuthSkip();
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
else {
|
|
333
|
+
// User chose not to skip after max retries, exit
|
|
334
|
+
this.log('');
|
|
335
|
+
this.log(chalk.yellow('⚠') + ' Setup cannot continue without authentication.');
|
|
336
|
+
this.log(chalk.gray("Run 'hd auth login' to authenticate separately."));
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
spinner.fail('Authentication failed');
|
|
344
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
345
|
+
this.log(chalk.red('✗') + ` Authentication failed: ${errorMessage}`);
|
|
346
|
+
// Check if we should retry or offer skip
|
|
347
|
+
if (attempts < MAX_RETRIES) {
|
|
348
|
+
const { retry } = await inquirer.prompt([
|
|
349
|
+
{
|
|
350
|
+
default: true,
|
|
351
|
+
message: 'Authentication failed. Would you like to retry?',
|
|
352
|
+
name: 'retry',
|
|
353
|
+
type: 'confirm',
|
|
354
|
+
},
|
|
355
|
+
]);
|
|
356
|
+
if (!retry) {
|
|
357
|
+
// User chose not to retry, offer skip
|
|
358
|
+
await this.handleAuthSkip();
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
// Max retries reached, offer skip
|
|
364
|
+
const { skip } = await inquirer.prompt([
|
|
365
|
+
{
|
|
366
|
+
default: false,
|
|
367
|
+
message: 'Authentication failed 3 times. Skip and continue?',
|
|
368
|
+
name: 'skip',
|
|
369
|
+
type: 'confirm',
|
|
370
|
+
},
|
|
371
|
+
]);
|
|
372
|
+
if (skip) {
|
|
373
|
+
await this.handleAuthSkip();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
// User chose not to skip after max retries, exit
|
|
378
|
+
this.log('');
|
|
379
|
+
this.log(chalk.yellow('⚠') + ' Setup cannot continue without authentication.');
|
|
380
|
+
this.log(chalk.gray("Run 'hd auth login' to authenticate separately."));
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Configure tenant domain - prompts for input or offers to keep existing
|
|
389
|
+
* Returns the configured domain for potential use in subsequent steps
|
|
390
|
+
*/
|
|
391
|
+
async configureTenantDomain() {
|
|
392
|
+
const existingDomain = this.tenantService.getTenantDomain();
|
|
393
|
+
let domain;
|
|
394
|
+
if (existingDomain) {
|
|
395
|
+
// Existing domain found - offer to keep or change
|
|
396
|
+
const { action } = await inquirer.prompt([
|
|
397
|
+
{
|
|
398
|
+
choices: [
|
|
399
|
+
{ name: `Keep current domain (${existingDomain})`, value: 'keep' },
|
|
400
|
+
{ name: 'Enter a different domain', value: 'change' },
|
|
401
|
+
],
|
|
402
|
+
message: `Tenant domain already configured: ${chalk.cyan(existingDomain)}`,
|
|
403
|
+
name: 'action',
|
|
404
|
+
type: 'list',
|
|
405
|
+
},
|
|
406
|
+
]);
|
|
407
|
+
if (action === 'keep') {
|
|
408
|
+
domain = existingDomain;
|
|
409
|
+
this.log(chalk.green('✓') + ' Tenant domain configured: ' + chalk.cyan(domain));
|
|
410
|
+
return domain;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
// Prompt for new domain
|
|
414
|
+
const { newDomain } = await inquirer.prompt([
|
|
415
|
+
{
|
|
416
|
+
message: 'Enter your tenant domain (e.g., acme.hyperdrive.bot):',
|
|
417
|
+
name: 'newDomain',
|
|
418
|
+
type: 'input',
|
|
419
|
+
validate: (input) => {
|
|
420
|
+
if (validateTenantDomain(input)) {
|
|
421
|
+
return true;
|
|
422
|
+
}
|
|
423
|
+
return 'Invalid domain format. Use subdomain.hyperdrive.bot or a valid custom domain';
|
|
424
|
+
},
|
|
425
|
+
},
|
|
426
|
+
]);
|
|
427
|
+
domain = newDomain.trim();
|
|
428
|
+
// Save the domain to config
|
|
429
|
+
this.tenantService.setTenantDomain(domain);
|
|
430
|
+
this.log(chalk.green('✓') + ' Tenant domain configured: ' + chalk.cyan(domain));
|
|
431
|
+
return domain;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Connect Git provider step in the init wizard
|
|
435
|
+
*
|
|
436
|
+
* Implements:
|
|
437
|
+
* - Provider selection prompt (GitHub, GitLab, Skip)
|
|
438
|
+
* - OAuth flow execution with spinner
|
|
439
|
+
* - Retry logic on failure
|
|
440
|
+
* - Skip option with helpful message
|
|
441
|
+
*/
|
|
442
|
+
async connectGitProvider() {
|
|
443
|
+
this.log(chalk.blue('Git Provider Setup'));
|
|
444
|
+
this.log(chalk.dim('Connect GitHub or GitLab for automated deployments'));
|
|
445
|
+
this.log('');
|
|
446
|
+
// Check if authentication was skipped - warn user but allow to try
|
|
447
|
+
if (this.authSkipped) {
|
|
448
|
+
this.log(chalk.yellow('⚠') + ' Authentication was skipped. Git provider setup requires authentication.');
|
|
449
|
+
const { tryAnyway } = await inquirer.prompt([{
|
|
450
|
+
default: false,
|
|
451
|
+
message: 'Would you like to try connecting a Git provider anyway?',
|
|
452
|
+
name: 'tryAnyway',
|
|
453
|
+
type: 'confirm',
|
|
454
|
+
}]);
|
|
455
|
+
if (!tryAnyway) {
|
|
456
|
+
this.gitSkipped = true;
|
|
457
|
+
this.log(chalk.yellow('⚠') + " Skipped Git provider setup. Run 'hd git connect' later.");
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Prompt for provider selection (includes Skip option)
|
|
462
|
+
const provider = await promptGitProvider(true);
|
|
463
|
+
if (provider === 'skip') {
|
|
464
|
+
this.gitSkipped = true;
|
|
465
|
+
this.log(chalk.yellow('⚠') + " Skipped Git provider setup. Run 'hd git connect' later.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
// Execute OAuth flow with retry logic
|
|
469
|
+
await this.executeGitOAuthFlow(provider);
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Connect Jira integration step in the init wizard
|
|
473
|
+
*
|
|
474
|
+
* Implements:
|
|
475
|
+
* 1. Prompt to connect Jira (includes Skip option)
|
|
476
|
+
* 2. Prompt for Jira domain
|
|
477
|
+
* 3. Pre-register domain with API
|
|
478
|
+
* 4. Display marketplace URL for app installation
|
|
479
|
+
*/
|
|
480
|
+
async connectJira() {
|
|
481
|
+
this.log(chalk.blue('Jira Integration Setup'));
|
|
482
|
+
this.log(chalk.dim('Connect Jira for automated project management integration'));
|
|
483
|
+
this.log('');
|
|
484
|
+
// Check if authentication was skipped - warn user but allow to try
|
|
485
|
+
if (this.authSkipped) {
|
|
486
|
+
this.log(chalk.yellow('⚠') + ' Authentication was skipped. Jira integration requires authentication.');
|
|
487
|
+
const { tryAnyway } = await inquirer.prompt([{
|
|
488
|
+
default: false,
|
|
489
|
+
message: 'Would you like to try connecting Jira anyway?',
|
|
490
|
+
name: 'tryAnyway',
|
|
491
|
+
type: 'confirm',
|
|
492
|
+
}]);
|
|
493
|
+
if (!tryAnyway) {
|
|
494
|
+
this.jiraSkipped = true;
|
|
495
|
+
this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
// Prompt to connect or skip
|
|
500
|
+
const action = await promptJiraConnect(true);
|
|
501
|
+
if (action === 'skip') {
|
|
502
|
+
this.jiraSkipped = true;
|
|
503
|
+
this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
// Execute Jira connection flow with retry limit
|
|
507
|
+
await this.executeJiraConnect(0);
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* Display summary of connected accounts
|
|
511
|
+
*/
|
|
512
|
+
displayAccountSummary() {
|
|
513
|
+
this.log('');
|
|
514
|
+
if (this.connectedAccounts.length > 0) {
|
|
515
|
+
this.log(chalk.green('✓') + ` Connected ${this.connectedAccounts.length} AWS account(s)`);
|
|
516
|
+
for (const account of this.connectedAccounts) {
|
|
517
|
+
this.log(chalk.gray(` - ${account.accountId}${account.alias ? ` (${account.alias})` : ''}`));
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
else {
|
|
521
|
+
this.log(chalk.yellow('⚠') + ' No AWS accounts connected');
|
|
522
|
+
this.log(chalk.gray(" Run 'hd account add' later to connect AWS accounts."));
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Display welcome message with Hyperdrive branding and setup steps
|
|
527
|
+
*/
|
|
528
|
+
displayWelcome() {
|
|
529
|
+
this.log('');
|
|
530
|
+
this.log(chalk.blue('╔═══════════════════════════════════════════════════════════════╗'));
|
|
531
|
+
this.log(chalk.blue('║') + chalk.white.bold(' Welcome to Hyperdrive CLI Setup ') + chalk.blue('║'));
|
|
532
|
+
this.log(chalk.blue('╚═══════════════════════════════════════════════════════════════╝'));
|
|
533
|
+
this.log('');
|
|
534
|
+
this.log(chalk.white('This wizard will guide you through configuring Hyperdrive CLI.'));
|
|
535
|
+
this.log(chalk.white('The following steps will be completed:'));
|
|
536
|
+
this.log('');
|
|
537
|
+
this.log(chalk.cyan(' 1. Tenant Domain'));
|
|
538
|
+
this.log(chalk.gray(' Configure your organization\'s Hyperdrive tenant domain'));
|
|
539
|
+
this.log('');
|
|
540
|
+
this.log(chalk.cyan(' 2. Authentication'));
|
|
541
|
+
this.log(chalk.gray(' Sign in to your Hyperdrive account using OAuth'));
|
|
542
|
+
this.log('');
|
|
543
|
+
this.log(chalk.cyan(' 3. AWS Accounts'));
|
|
544
|
+
this.log(chalk.gray(' Link AWS accounts for deployment targets'));
|
|
545
|
+
this.log('');
|
|
546
|
+
this.log(chalk.cyan(' 4. Git Provider'));
|
|
547
|
+
this.log(chalk.gray(' Connect your Git provider for repository access'));
|
|
548
|
+
this.log('');
|
|
549
|
+
this.log(chalk.cyan(' 5. Jira Integration'));
|
|
550
|
+
this.log(chalk.gray(' Connect Jira for project management integration'));
|
|
551
|
+
this.log('');
|
|
552
|
+
}
|
|
553
|
+
/**
|
|
554
|
+
* Execute Git OAuth flow with retry logic
|
|
555
|
+
*
|
|
556
|
+
* @param provider - Git provider to connect (github or gitlab)
|
|
557
|
+
* @param attemptCount - Current retry attempt (0-indexed)
|
|
558
|
+
*/
|
|
559
|
+
async executeGitOAuthFlow(provider, attemptCount = 0) {
|
|
560
|
+
const providerName = provider === 'github' ? 'GitHub' : 'GitLab';
|
|
561
|
+
const spinner = ora(`Connecting to ${providerName}...`).start();
|
|
562
|
+
try {
|
|
563
|
+
const result = await getGitConnectImpl()({
|
|
564
|
+
logger: (message) => {
|
|
565
|
+
spinner.text = message;
|
|
566
|
+
},
|
|
567
|
+
provider,
|
|
568
|
+
});
|
|
569
|
+
if (result.success) {
|
|
570
|
+
spinner.succeed(`Connected to ${providerName}`);
|
|
571
|
+
// Display connected account info
|
|
572
|
+
if (result.accountName) {
|
|
573
|
+
this.log(chalk.green('✓') + ` Linked ${result.accountName}`);
|
|
574
|
+
}
|
|
575
|
+
// List connected installations if available
|
|
576
|
+
if (result.installations && result.installations.length > 0) {
|
|
577
|
+
for (const installation of result.installations) {
|
|
578
|
+
const accountName = installation.accountLogin || installation.gitlabUsername || 'Unknown';
|
|
579
|
+
this.log(chalk.gray(` - ${accountName} (${installation.provider})`));
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
// Store connected provider info for summary
|
|
583
|
+
this.connectedGitProvider = {
|
|
584
|
+
accountName: result.accountName,
|
|
585
|
+
provider: result.provider,
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
else {
|
|
589
|
+
spinner.fail(`Failed to connect to ${providerName}`);
|
|
590
|
+
this.log(chalk.red('✗') + ` Error: ${result.error || 'Unknown error'}`);
|
|
591
|
+
// Offer retry
|
|
592
|
+
await this.handleGitRetry(provider, attemptCount);
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
catch (error) {
|
|
596
|
+
spinner.fail(`Failed to connect to ${providerName}`);
|
|
597
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
598
|
+
this.log(chalk.red('✗') + ` Error: ${errorMessage}`);
|
|
599
|
+
// Offer retry
|
|
600
|
+
await this.handleGitRetry(provider, attemptCount);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Execute Jira connection with proper separation of concerns
|
|
605
|
+
*
|
|
606
|
+
* Architecture:
|
|
607
|
+
* 1. Prompt for Jira domain (no spinner - user needs to see prompt)
|
|
608
|
+
* 2. Register domain with API (with spinner - actual work happening)
|
|
609
|
+
* 3. Display marketplace URL and next steps
|
|
610
|
+
*
|
|
611
|
+
* @param attemptCount - Current retry attempt (0-indexed)
|
|
612
|
+
*/
|
|
613
|
+
async executeJiraConnect(attemptCount = 0) {
|
|
614
|
+
const MAX_RETRIES = 3;
|
|
615
|
+
try {
|
|
616
|
+
// Step 1: Collect Jira domain from user (NO SPINNER - user needs to see prompt!)
|
|
617
|
+
const domainData = await promptJiraDomain();
|
|
618
|
+
// Step 2: Register domain with API (WITH SPINNER - actual API work)
|
|
619
|
+
const spinner = ora('Registering Jira domain...').start();
|
|
620
|
+
const result = await registerJiraDomain(domainData.jiraDomain);
|
|
621
|
+
if (!result.success) {
|
|
622
|
+
spinner.fail('Failed to register Jira domain');
|
|
623
|
+
this.log(chalk.red('✗') + ` Error: ${result.error || 'Unknown error'}`);
|
|
624
|
+
// Offer retry if under max attempts
|
|
625
|
+
await this.handleJiraRetry(attemptCount);
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
spinner.succeed('Jira domain registered successfully');
|
|
629
|
+
this.log(chalk.green('✓') + ` Domain ${result.jiraDomain} registered`);
|
|
630
|
+
// Store connected Jira info for summary
|
|
631
|
+
this.connectedJira = {
|
|
632
|
+
jiraDomain: result.jiraDomain,
|
|
633
|
+
registrationToken: result.registrationToken,
|
|
634
|
+
};
|
|
635
|
+
// Display next steps
|
|
636
|
+
this.log('');
|
|
637
|
+
this.log(chalk.bold('📋 Next Steps:'));
|
|
638
|
+
this.log(` ${chalk.cyan('1.')} Install the Hyperdrive Forge app from the Atlassian Marketplace`);
|
|
639
|
+
this.log(` ${chalk.cyan('2.')} Enter the registration token during installation`);
|
|
640
|
+
this.log('');
|
|
641
|
+
this.log(chalk.bold('Marketplace URL:'));
|
|
642
|
+
this.log(` ${chalk.cyan(result.marketplaceUrl || 'https://marketplace.atlassian.com')}`);
|
|
643
|
+
this.log('');
|
|
644
|
+
this.log(chalk.dim('💡 Tip: Run') + chalk.cyan(' hd jira status ') + chalk.dim('to verify the connection'));
|
|
645
|
+
this.log('');
|
|
646
|
+
}
|
|
647
|
+
catch (error) {
|
|
648
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
649
|
+
this.log(chalk.red('✗') + ` Error: ${errorMessage}`);
|
|
650
|
+
// Offer retry if under max attempts
|
|
651
|
+
await this.handleJiraRetry(attemptCount);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Handle authentication skip - set flag and display warning
|
|
656
|
+
*/
|
|
657
|
+
async handleAuthSkip() {
|
|
658
|
+
this.authSkipped = true;
|
|
659
|
+
this.log('');
|
|
660
|
+
this.log(chalk.yellow('⚠') + " Authentication skipped. You'll need to run 'hd auth login' to use most commands.");
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Handle Git connection failure with retry option
|
|
664
|
+
*
|
|
665
|
+
* @param provider - Git provider to connect (github or gitlab)
|
|
666
|
+
* @param attemptCount - Current retry attempt (0-indexed)
|
|
667
|
+
*/
|
|
668
|
+
async handleGitRetry(provider, attemptCount) {
|
|
669
|
+
const MAX_RETRIES = 3;
|
|
670
|
+
if (attemptCount >= MAX_RETRIES) {
|
|
671
|
+
// Max retries reached
|
|
672
|
+
const { skip } = await inquirer.prompt([{
|
|
673
|
+
default: false,
|
|
674
|
+
message: 'Git connection failed 3 times. Skip and continue?',
|
|
675
|
+
name: 'skip',
|
|
676
|
+
type: 'confirm',
|
|
677
|
+
}]);
|
|
678
|
+
if (skip) {
|
|
679
|
+
this.gitSkipped = true;
|
|
680
|
+
this.log(chalk.yellow('⚠') + " Skipped Git provider setup. Run 'hd git connect' later.");
|
|
681
|
+
}
|
|
682
|
+
else {
|
|
683
|
+
this.log('');
|
|
684
|
+
this.log(chalk.yellow('⚠') + ' Setup cannot continue without Git provider.');
|
|
685
|
+
this.log(chalk.gray("Run 'hd git connect' to connect Git separately."));
|
|
686
|
+
}
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
// Under max retries - offer retry
|
|
690
|
+
const { retry } = await inquirer.prompt([{
|
|
691
|
+
default: true,
|
|
692
|
+
message: 'Would you like to retry?',
|
|
693
|
+
name: 'retry',
|
|
694
|
+
type: 'confirm',
|
|
695
|
+
}]);
|
|
696
|
+
if (retry) {
|
|
697
|
+
// Re-execute OAuth flow for same provider with incremented attempt count
|
|
698
|
+
await this.executeGitOAuthFlow(provider, attemptCount + 1);
|
|
699
|
+
}
|
|
700
|
+
else {
|
|
701
|
+
// User chose not to retry - mark as skipped
|
|
702
|
+
this.gitSkipped = true;
|
|
703
|
+
this.log(chalk.yellow('⚠') + " Skipped Git provider setup. Run 'hd git connect' later.");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Handle Jira connection failure with retry option
|
|
708
|
+
*
|
|
709
|
+
* @param attemptCount - Current retry attempt (0-indexed)
|
|
710
|
+
*/
|
|
711
|
+
async handleJiraRetry(attemptCount) {
|
|
712
|
+
const MAX_RETRIES = 3;
|
|
713
|
+
if (attemptCount >= MAX_RETRIES) {
|
|
714
|
+
// Max retries reached
|
|
715
|
+
const { skip } = await inquirer.prompt([{
|
|
716
|
+
default: false,
|
|
717
|
+
message: 'Jira connection failed 3 times. Skip and continue?',
|
|
718
|
+
name: 'skip',
|
|
719
|
+
type: 'confirm',
|
|
720
|
+
}]);
|
|
721
|
+
if (skip) {
|
|
722
|
+
this.jiraSkipped = true;
|
|
723
|
+
this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
|
|
724
|
+
}
|
|
725
|
+
else {
|
|
726
|
+
this.log('');
|
|
727
|
+
this.log(chalk.yellow('⚠') + ' Setup cannot continue without Jira connection.');
|
|
728
|
+
this.log(chalk.gray("Run 'hd jira connect' to connect Jira separately."));
|
|
729
|
+
}
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
// Under max retries - offer retry
|
|
733
|
+
const { retry } = await inquirer.prompt([{
|
|
734
|
+
default: true,
|
|
735
|
+
message: 'Would you like to retry?',
|
|
736
|
+
name: 'retry',
|
|
737
|
+
type: 'confirm',
|
|
738
|
+
}]);
|
|
739
|
+
if (retry) {
|
|
740
|
+
// Re-execute Jira connection with incremented attempt count
|
|
741
|
+
await this.executeJiraConnect(attemptCount + 1);
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
// User chose not to retry - mark as skipped
|
|
745
|
+
this.jiraSkipped = true;
|
|
746
|
+
this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
/**
|
|
750
|
+
* Handle cross-account IAM role creation
|
|
751
|
+
*/
|
|
752
|
+
async handleRoleCreation(result) {
|
|
753
|
+
this.log('');
|
|
754
|
+
this.log(chalk.yellow('⚠') + ' A cross-account IAM role needs to be created.');
|
|
755
|
+
const { openBrowser } = await inquirer.prompt([{
|
|
756
|
+
default: true,
|
|
757
|
+
message: 'Open CloudFormation in browser to create the role?',
|
|
758
|
+
name: 'openBrowser',
|
|
759
|
+
type: 'confirm',
|
|
760
|
+
}]);
|
|
761
|
+
if (openBrowser) {
|
|
762
|
+
this.log(chalk.gray('Opening browser for CloudFormation stack creation...'));
|
|
763
|
+
this.log(chalk.dim('If browser doesn\'t open or opens in wrong profile, copy this URL:'));
|
|
764
|
+
this.log(chalk.cyan(` ${result.quickCreateUrl}`));
|
|
765
|
+
this.log('');
|
|
766
|
+
await openCloudFormationUrl(result.quickCreateUrl);
|
|
767
|
+
// Wait for role verification
|
|
768
|
+
const verifySpinner = ora('Waiting for role verification...').start();
|
|
769
|
+
const verifyResult = await waitForRoleVerification(result.accountId, (message) => { verifySpinner.text = message; });
|
|
770
|
+
if (verifyResult.verified) {
|
|
771
|
+
verifySpinner.succeed('Cross-account role verified');
|
|
772
|
+
this.log(chalk.green('✓') + ' Role verified successfully');
|
|
773
|
+
}
|
|
774
|
+
else {
|
|
775
|
+
verifySpinner.warn('Role verification timed out');
|
|
776
|
+
this.log(chalk.yellow('⚠') + ' Role may still be creating. You can verify later with:');
|
|
777
|
+
this.log(chalk.gray(` hd account verify --accountId ${result.accountId}`));
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
this.log(chalk.gray('You can create the role later by running:'));
|
|
782
|
+
this.log(chalk.gray(` hd account verify --accountId ${result.accountId}`));
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Prompt user to continue or exit the setup wizard
|
|
787
|
+
*/
|
|
788
|
+
async promptContinue() {
|
|
789
|
+
const answers = await inquirer.prompt([
|
|
790
|
+
{
|
|
791
|
+
default: true,
|
|
792
|
+
message: 'Ready to continue with setup?',
|
|
793
|
+
name: 'continue',
|
|
794
|
+
type: 'confirm',
|
|
795
|
+
},
|
|
796
|
+
]);
|
|
797
|
+
return answers.continue;
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Display final setup summary using the summary-display utility
|
|
801
|
+
*/
|
|
802
|
+
showSummary(tenantDomain) {
|
|
803
|
+
const results = {
|
|
804
|
+
accountsSkipped: this.accountsSkipped,
|
|
805
|
+
authCompleted: !this.authSkipped,
|
|
806
|
+
authSkipped: this.authSkipped,
|
|
807
|
+
connectedAccounts: this.connectedAccounts,
|
|
808
|
+
gitAccountName: this.connectedGitProvider?.accountName,
|
|
809
|
+
gitProvider: this.connectedGitProvider?.provider,
|
|
810
|
+
gitSkipped: this.gitSkipped,
|
|
811
|
+
jiraDomain: this.connectedJira?.jiraDomain,
|
|
812
|
+
jiraSkipped: this.jiraSkipped,
|
|
813
|
+
tenantDomain,
|
|
814
|
+
};
|
|
815
|
+
displaySetupSummary(results, (message) => this.log(message));
|
|
816
|
+
}
|
|
817
|
+
}
|