@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,232 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import http from 'node:http';
|
|
3
|
+
import open from 'open';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../services/hyperdrive-sigv4.js';
|
|
5
|
+
// Module-level callback server instance for cleanup
|
|
6
|
+
let callbackServer = null;
|
|
7
|
+
/**
|
|
8
|
+
* Prompt user to select a Git provider
|
|
9
|
+
*
|
|
10
|
+
* @param includeSkip - Whether to include a "Skip for now" option
|
|
11
|
+
* @returns Selected provider or 'skip'
|
|
12
|
+
*/
|
|
13
|
+
export async function promptGitProvider(includeSkip = false) {
|
|
14
|
+
const choices = [
|
|
15
|
+
{ name: 'GitHub', value: 'github' },
|
|
16
|
+
{ name: 'GitLab', value: 'gitlab' },
|
|
17
|
+
];
|
|
18
|
+
if (includeSkip) {
|
|
19
|
+
choices.push({ name: 'Skip for now', value: 'skip' });
|
|
20
|
+
}
|
|
21
|
+
const response = await inquirer.prompt([{
|
|
22
|
+
choices,
|
|
23
|
+
message: 'Which Git provider would you like to connect?',
|
|
24
|
+
name: 'provider',
|
|
25
|
+
type: 'list',
|
|
26
|
+
}]);
|
|
27
|
+
return response.provider;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Generate HTML response for callback page
|
|
31
|
+
*/
|
|
32
|
+
function getCallbackHtml(success, error) {
|
|
33
|
+
const title = success ? 'Authorization Successful' : 'Authorization Failed';
|
|
34
|
+
const message = success
|
|
35
|
+
? 'You can close this window and return to the CLI.'
|
|
36
|
+
: `Error: ${error || 'Unknown error'}. Please try again.`;
|
|
37
|
+
const color = success ? '#22c55e' : '#ef4444';
|
|
38
|
+
return `
|
|
39
|
+
<!DOCTYPE html>
|
|
40
|
+
<html>
|
|
41
|
+
<head>
|
|
42
|
+
<title>${title}</title>
|
|
43
|
+
<style>
|
|
44
|
+
body {
|
|
45
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: center;
|
|
49
|
+
min-height: 100vh;
|
|
50
|
+
margin: 0;
|
|
51
|
+
background: #1a1a2e;
|
|
52
|
+
color: #fff;
|
|
53
|
+
}
|
|
54
|
+
.container {
|
|
55
|
+
text-align: center;
|
|
56
|
+
padding: 2rem;
|
|
57
|
+
}
|
|
58
|
+
.icon {
|
|
59
|
+
font-size: 4rem;
|
|
60
|
+
margin-bottom: 1rem;
|
|
61
|
+
}
|
|
62
|
+
h1 {
|
|
63
|
+
color: ${color};
|
|
64
|
+
margin-bottom: 0.5rem;
|
|
65
|
+
}
|
|
66
|
+
p {
|
|
67
|
+
color: #a0a0a0;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div class="container">
|
|
73
|
+
<div class="icon">${success ? '✅' : '❌'}</div>
|
|
74
|
+
<h1>${title}</h1>
|
|
75
|
+
<p>${message}</p>
|
|
76
|
+
</div>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Stop the callback server if running
|
|
83
|
+
*/
|
|
84
|
+
export function stopCallbackServer() {
|
|
85
|
+
if (callbackServer) {
|
|
86
|
+
callbackServer.close();
|
|
87
|
+
callbackServer = null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Start local HTTP server to receive OAuth callback
|
|
92
|
+
*
|
|
93
|
+
* @param expectedState - State parameter to validate against
|
|
94
|
+
* @param port - Port to listen on (default: 8765)
|
|
95
|
+
* @param timeout - Timeout in milliseconds (default: 5 minutes)
|
|
96
|
+
* @param logger - Optional logging function
|
|
97
|
+
* @returns Promise resolving with callback result
|
|
98
|
+
*/
|
|
99
|
+
export function waitForCallback(expectedState, port = 8765, timeout = 5 * 60 * 1000, logger) {
|
|
100
|
+
return new Promise((resolve) => {
|
|
101
|
+
const timeoutId = setTimeout(() => {
|
|
102
|
+
stopCallbackServer();
|
|
103
|
+
resolve({ error: 'Timeout waiting for callback', success: false });
|
|
104
|
+
}, timeout);
|
|
105
|
+
callbackServer = http.createServer((req, res) => {
|
|
106
|
+
const url = new URL(req.url || '', `http://localhost:${port}`);
|
|
107
|
+
if (url.pathname === '/callback' || url.pathname === '/git/callback') {
|
|
108
|
+
const state = url.searchParams.get('state');
|
|
109
|
+
const error = url.searchParams.get('error');
|
|
110
|
+
const success = url.searchParams.get('success');
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
if (error) {
|
|
113
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
114
|
+
res.end(getCallbackHtml(false, error));
|
|
115
|
+
resolve({ error, success: false });
|
|
116
|
+
}
|
|
117
|
+
else if (state !== expectedState) {
|
|
118
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
119
|
+
res.end(getCallbackHtml(false, 'Invalid state parameter'));
|
|
120
|
+
resolve({ error: 'Invalid state', success: false });
|
|
121
|
+
}
|
|
122
|
+
else if (success === 'true') {
|
|
123
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
124
|
+
res.end(getCallbackHtml(true));
|
|
125
|
+
resolve({ success: true });
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
129
|
+
res.end(getCallbackHtml(false, 'Unknown error'));
|
|
130
|
+
resolve({ error: 'Unknown error', success: false });
|
|
131
|
+
}
|
|
132
|
+
// Give time for the response to be sent before shutting down
|
|
133
|
+
setTimeout(() => stopCallbackServer(), 1000);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
res.writeHead(404);
|
|
137
|
+
res.end('Not found');
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
callbackServer.listen(port, () => {
|
|
141
|
+
if (logger) {
|
|
142
|
+
logger(`Waiting for authorization callback on http://localhost:${port}...`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Execute the Git provider OAuth connection flow
|
|
149
|
+
*
|
|
150
|
+
* This function handles:
|
|
151
|
+
* 1. Provider selection (if not specified)
|
|
152
|
+
* 2. OAuth initiation via API
|
|
153
|
+
* 3. Starting local callback server
|
|
154
|
+
* 4. Opening browser for authorization
|
|
155
|
+
* 5. Waiting for callback with timeout
|
|
156
|
+
* 6. Fetching and returning connected installations
|
|
157
|
+
*
|
|
158
|
+
* @param options - Configuration options for the Git connect flow
|
|
159
|
+
* @returns GitConnectResult indicating success or failure
|
|
160
|
+
*/
|
|
161
|
+
export async function executeGitConnect(options = {}) {
|
|
162
|
+
const { callbackPort = 8765, logger = () => { }, // No-op by default
|
|
163
|
+
provider: initialProvider, timeout = 5 * 60 * 1000, } = options;
|
|
164
|
+
try {
|
|
165
|
+
// Step 1: Determine provider (prompt if not specified)
|
|
166
|
+
let provider;
|
|
167
|
+
if (initialProvider) {
|
|
168
|
+
provider = initialProvider;
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
const selected = await promptGitProvider(false);
|
|
172
|
+
if (selected === 'skip') {
|
|
173
|
+
return { skipped: true, success: false };
|
|
174
|
+
}
|
|
175
|
+
provider = selected;
|
|
176
|
+
}
|
|
177
|
+
// Step 2: Initialize API service
|
|
178
|
+
const service = new HyperdriveSigV4Service();
|
|
179
|
+
// Step 3: Initiate OAuth flow via API
|
|
180
|
+
const authResponse = await service.gitAuthInitiate(provider);
|
|
181
|
+
logger(`Opening browser for ${provider === 'github' ? 'GitHub' : 'GitLab'} authorization...`);
|
|
182
|
+
logger(`If browser doesn't open or opens in wrong profile, copy this URL:`);
|
|
183
|
+
logger(` ${authResponse.installUrl}`);
|
|
184
|
+
// Step 4: Start callback server and wait for result
|
|
185
|
+
const callbackPromise = waitForCallback(authResponse.state, callbackPort, timeout, logger);
|
|
186
|
+
// Step 5: Open browser
|
|
187
|
+
await open(authResponse.installUrl);
|
|
188
|
+
// Step 6: Wait for callback
|
|
189
|
+
const result = await callbackPromise;
|
|
190
|
+
if (result.success) {
|
|
191
|
+
// Step 7: Fetch installations to get account name
|
|
192
|
+
const installationsResponse = await service.gitListInstallations(provider);
|
|
193
|
+
const installations = installationsResponse.installations || [];
|
|
194
|
+
// Determine primary account name from first installation
|
|
195
|
+
let accountName;
|
|
196
|
+
if (installations.length > 0) {
|
|
197
|
+
const firstInstall = installations[0];
|
|
198
|
+
accountName = firstInstall.accountLogin || firstInstall.gitlabUsername || undefined;
|
|
199
|
+
}
|
|
200
|
+
// Map installations to simplified format
|
|
201
|
+
const installationInfos = installations.map(inst => ({
|
|
202
|
+
accountLogin: inst.accountLogin,
|
|
203
|
+
gitlabUsername: inst.gitlabUsername,
|
|
204
|
+
provider: inst.provider,
|
|
205
|
+
}));
|
|
206
|
+
return {
|
|
207
|
+
accountName,
|
|
208
|
+
installations: installationInfos,
|
|
209
|
+
provider,
|
|
210
|
+
success: true,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
return {
|
|
215
|
+
error: result.error || 'Unknown error during authorization',
|
|
216
|
+
provider,
|
|
217
|
+
success: false,
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
223
|
+
return {
|
|
224
|
+
error: errorMessage,
|
|
225
|
+
success: false,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
// Ensure server is stopped
|
|
230
|
+
stopCallbackServer();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Response from Jira pre-registration API
|
|
3
|
+
*/
|
|
4
|
+
export interface PreRegisterResponse {
|
|
5
|
+
nextSteps: {
|
|
6
|
+
instructions: string[];
|
|
7
|
+
marketplaceUrl: string;
|
|
8
|
+
};
|
|
9
|
+
registration: {
|
|
10
|
+
jiraDomain: string;
|
|
11
|
+
status: string;
|
|
12
|
+
tenantId: string;
|
|
13
|
+
token: string;
|
|
14
|
+
};
|
|
15
|
+
success: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Result of the Jira connect flow
|
|
19
|
+
*/
|
|
20
|
+
export interface JiraConnectResult {
|
|
21
|
+
error?: string;
|
|
22
|
+
jiraDomain?: string;
|
|
23
|
+
marketplaceUrl?: string;
|
|
24
|
+
registrationToken?: string;
|
|
25
|
+
success: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Jira domain data
|
|
29
|
+
*/
|
|
30
|
+
export interface JiraDomainData {
|
|
31
|
+
jiraDomain: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Validate and normalize Jira domain
|
|
35
|
+
*
|
|
36
|
+
* Ensures domain matches pattern: subdomain.atlassian.net
|
|
37
|
+
* - Must have subdomain (alphanumeric + hyphens)
|
|
38
|
+
* - Must end with .atlassian.net exactly
|
|
39
|
+
* - No additional paths or domains
|
|
40
|
+
*/
|
|
41
|
+
export declare function validateJiraDomain(input: string): boolean | string;
|
|
42
|
+
/**
|
|
43
|
+
* Normalize Jira domain input
|
|
44
|
+
*/
|
|
45
|
+
export declare function normalizeJiraDomain(input: string): string;
|
|
46
|
+
/**
|
|
47
|
+
* Prompt user for Jira domain
|
|
48
|
+
*
|
|
49
|
+
* Separated from API call to allow proper spinner timing
|
|
50
|
+
*/
|
|
51
|
+
export declare function promptJiraDomain(): Promise<JiraDomainData>;
|
|
52
|
+
/**
|
|
53
|
+
* Prompt for whether to connect Jira (with skip option)
|
|
54
|
+
*/
|
|
55
|
+
export declare function promptJiraConnect(includeSkip?: boolean): Promise<'skip' | 'yes'>;
|
|
56
|
+
/**
|
|
57
|
+
* Register Jira domain with Hyperdrive API (no user prompts)
|
|
58
|
+
*
|
|
59
|
+
* This function is separated from user input collection to allow
|
|
60
|
+
* proper spinner timing and better separation of concerns.
|
|
61
|
+
*/
|
|
62
|
+
export declare function registerJiraDomain(jiraDomain: string): Promise<JiraConnectResult>;
|
|
63
|
+
/**
|
|
64
|
+
* Execute the Jira connect flow
|
|
65
|
+
*
|
|
66
|
+
* This function handles:
|
|
67
|
+
* 1. Prompting for Jira domain
|
|
68
|
+
* 2. Pre-registering the domain with Hyperdrive API
|
|
69
|
+
* 3. Returning result with marketplace URL
|
|
70
|
+
*/
|
|
71
|
+
export declare function executeJiraConnect(): Promise<JiraConnectResult>;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import { HyperdriveSigV4Service } from '../services/hyperdrive-sigv4.js';
|
|
3
|
+
/**
|
|
4
|
+
* Validate and normalize Jira domain
|
|
5
|
+
*
|
|
6
|
+
* Ensures domain matches pattern: subdomain.atlassian.net
|
|
7
|
+
* - Must have subdomain (alphanumeric + hyphens)
|
|
8
|
+
* - Must end with .atlassian.net exactly
|
|
9
|
+
* - No additional paths or domains
|
|
10
|
+
*/
|
|
11
|
+
export function validateJiraDomain(input) {
|
|
12
|
+
const normalized = normalizeJiraDomain(input);
|
|
13
|
+
// Regex: subdomain.atlassian.net (subdomain can contain alphanumeric and hyphens)
|
|
14
|
+
const jiraPattern = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?\.atlassian\.net$/i;
|
|
15
|
+
if (!jiraPattern.test(normalized)) {
|
|
16
|
+
return 'Invalid Jira domain. Expected format: your-company.atlassian.net';
|
|
17
|
+
}
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Normalize Jira domain input
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeJiraDomain(input) {
|
|
24
|
+
return input
|
|
25
|
+
.replace(/^https?:\/\//, '')
|
|
26
|
+
.replace(/\/$/, '')
|
|
27
|
+
.toLowerCase();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Prompt user for Jira domain
|
|
31
|
+
*
|
|
32
|
+
* Separated from API call to allow proper spinner timing
|
|
33
|
+
*/
|
|
34
|
+
export async function promptJiraDomain() {
|
|
35
|
+
const answers = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
default: 'your-company.atlassian.net',
|
|
38
|
+
filter: normalizeJiraDomain,
|
|
39
|
+
message: 'Enter your Jira domain:',
|
|
40
|
+
name: 'jiraDomain',
|
|
41
|
+
type: 'input',
|
|
42
|
+
validate: validateJiraDomain,
|
|
43
|
+
},
|
|
44
|
+
]);
|
|
45
|
+
return { jiraDomain: answers.jiraDomain };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Prompt for whether to connect Jira (with skip option)
|
|
49
|
+
*/
|
|
50
|
+
export async function promptJiraConnect(includeSkip = false) {
|
|
51
|
+
if (!includeSkip) {
|
|
52
|
+
const { connect } = await inquirer.prompt([{
|
|
53
|
+
default: false,
|
|
54
|
+
message: 'Would you like to connect Jira?',
|
|
55
|
+
name: 'connect',
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
}]);
|
|
58
|
+
return connect ? 'yes' : 'skip';
|
|
59
|
+
}
|
|
60
|
+
const { action } = await inquirer.prompt([{
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: 'Yes, connect Jira now', value: 'yes' },
|
|
63
|
+
{ name: 'Skip for now', value: 'skip' },
|
|
64
|
+
],
|
|
65
|
+
default: 'skip',
|
|
66
|
+
message: 'Would you like to connect Jira?',
|
|
67
|
+
name: 'action',
|
|
68
|
+
type: 'list',
|
|
69
|
+
}]);
|
|
70
|
+
return action;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Register Jira domain with Hyperdrive API (no user prompts)
|
|
74
|
+
*
|
|
75
|
+
* This function is separated from user input collection to allow
|
|
76
|
+
* proper spinner timing and better separation of concerns.
|
|
77
|
+
*/
|
|
78
|
+
export async function registerJiraDomain(jiraDomain) {
|
|
79
|
+
try {
|
|
80
|
+
const service = new HyperdriveSigV4Service();
|
|
81
|
+
// Use the public API method instead of accessing private method
|
|
82
|
+
const response = await service.jiraPreRegister({ jiraDomain });
|
|
83
|
+
return {
|
|
84
|
+
jiraDomain: response.registration.jiraDomain,
|
|
85
|
+
marketplaceUrl: response.nextSteps.marketplaceUrl,
|
|
86
|
+
registrationToken: response.registration.token,
|
|
87
|
+
success: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
92
|
+
return {
|
|
93
|
+
error: errorMessage,
|
|
94
|
+
success: false,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Execute the Jira connect flow
|
|
100
|
+
*
|
|
101
|
+
* This function handles:
|
|
102
|
+
* 1. Prompting for Jira domain
|
|
103
|
+
* 2. Pre-registering the domain with Hyperdrive API
|
|
104
|
+
* 3. Returning result with marketplace URL
|
|
105
|
+
*/
|
|
106
|
+
export async function executeJiraConnect() {
|
|
107
|
+
try {
|
|
108
|
+
// Step 1: Prompt for Jira domain (no spinner - user needs to see prompts)
|
|
109
|
+
const domainData = await promptJiraDomain();
|
|
110
|
+
// Step 2: Register domain with API (caller should wrap this with spinner)
|
|
111
|
+
return await registerJiraDomain(domainData.jiraDomain);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
115
|
+
return {
|
|
116
|
+
error: errorMessage,
|
|
117
|
+
success: false,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a connected AWS account
|
|
3
|
+
*/
|
|
4
|
+
export interface ConnectedAccount {
|
|
5
|
+
accountId: string;
|
|
6
|
+
alias?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Results from the setup wizard steps
|
|
10
|
+
*/
|
|
11
|
+
export interface SetupResults {
|
|
12
|
+
/** Whether AWS account setup was skipped */
|
|
13
|
+
accountsSkipped: boolean;
|
|
14
|
+
/** Whether authentication completed successfully */
|
|
15
|
+
authCompleted: boolean;
|
|
16
|
+
/** Whether authentication was skipped */
|
|
17
|
+
authSkipped: boolean;
|
|
18
|
+
/** List of connected AWS accounts */
|
|
19
|
+
connectedAccounts: ConnectedAccount[];
|
|
20
|
+
/** Git account/organization name */
|
|
21
|
+
gitAccountName?: string;
|
|
22
|
+
/** Git provider that was connected */
|
|
23
|
+
gitProvider?: 'github' | 'gitlab';
|
|
24
|
+
/** Whether Git provider setup was skipped */
|
|
25
|
+
gitSkipped: boolean;
|
|
26
|
+
/** Jira domain that was connected */
|
|
27
|
+
jiraDomain?: string;
|
|
28
|
+
/** Whether Jira integration was skipped */
|
|
29
|
+
jiraSkipped: boolean;
|
|
30
|
+
/** Tenant domain that was configured */
|
|
31
|
+
tenantDomain: string;
|
|
32
|
+
/** Email of authenticated user (if available) */
|
|
33
|
+
userEmail?: string;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Represents a recommended next step
|
|
37
|
+
*/
|
|
38
|
+
export interface NextStep {
|
|
39
|
+
/** Command to run */
|
|
40
|
+
command: string;
|
|
41
|
+
/** Description of what the command does */
|
|
42
|
+
description: string;
|
|
43
|
+
/** Priority (lower = higher priority) */
|
|
44
|
+
priority: number;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get the list of recommended next steps based on setup results
|
|
48
|
+
*
|
|
49
|
+
* @param results - The setup results from the wizard
|
|
50
|
+
* @returns Array of next steps sorted by priority
|
|
51
|
+
*/
|
|
52
|
+
export declare function getNextSteps(results: SetupResults): NextStep[];
|
|
53
|
+
/**
|
|
54
|
+
* Display the setup summary with status and next steps
|
|
55
|
+
*
|
|
56
|
+
* @param results - The setup results from the wizard
|
|
57
|
+
* @param logger - Function to log output (defaults to console.log)
|
|
58
|
+
*/
|
|
59
|
+
export declare function displaySetupSummary(results: SetupResults, logger?: (message: string) => void): void;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/** Width for status labels in the summary display */
|
|
3
|
+
const STATUS_LABEL_WIDTH = 16;
|
|
4
|
+
/**
|
|
5
|
+
* Format a status label with consistent width
|
|
6
|
+
*/
|
|
7
|
+
const formatLabel = (label) => label.padEnd(STATUS_LABEL_WIDTH);
|
|
8
|
+
/**
|
|
9
|
+
* Get the list of recommended next steps based on setup results
|
|
10
|
+
*
|
|
11
|
+
* @param results - The setup results from the wizard
|
|
12
|
+
* @returns Array of next steps sorted by priority
|
|
13
|
+
*/
|
|
14
|
+
export function getNextSteps(results) {
|
|
15
|
+
const steps = [];
|
|
16
|
+
// Authentication is highest priority if skipped
|
|
17
|
+
if (results.authSkipped) {
|
|
18
|
+
steps.push({
|
|
19
|
+
command: 'hd auth login',
|
|
20
|
+
description: 'Required for CLI operations',
|
|
21
|
+
priority: 1,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
// AWS accounts are second priority
|
|
25
|
+
if (results.accountsSkipped || results.connectedAccounts.length === 0) {
|
|
26
|
+
steps.push({
|
|
27
|
+
command: 'hd account add',
|
|
28
|
+
description: 'Connect AWS accounts for deployments',
|
|
29
|
+
priority: 2,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
// Git provider is third priority
|
|
33
|
+
if (results.gitSkipped) {
|
|
34
|
+
steps.push({
|
|
35
|
+
command: 'hd git connect',
|
|
36
|
+
description: 'Link your Git provider for CI/CD',
|
|
37
|
+
priority: 3,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Jira integration is fourth priority
|
|
41
|
+
if (results.jiraSkipped) {
|
|
42
|
+
steps.push({
|
|
43
|
+
command: 'hd jira connect',
|
|
44
|
+
description: 'Connect Jira for project management',
|
|
45
|
+
priority: 4,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
// If all steps completed, suggest module create
|
|
49
|
+
if (steps.length === 0) {
|
|
50
|
+
steps.push({
|
|
51
|
+
command: 'hd module create',
|
|
52
|
+
description: 'Create your first serverless module',
|
|
53
|
+
priority: 1,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// Sort by priority
|
|
57
|
+
return steps.sort((a, b) => a.priority - b.priority);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Display the setup summary with status and next steps
|
|
61
|
+
*
|
|
62
|
+
* @param results - The setup results from the wizard
|
|
63
|
+
* @param logger - Function to log output (defaults to console.log)
|
|
64
|
+
*/
|
|
65
|
+
export function displaySetupSummary(results, logger = console.log) {
|
|
66
|
+
// Display header
|
|
67
|
+
logger('');
|
|
68
|
+
logger(chalk.blue('╔═══════════════════════════════════════════════════════════════╗'));
|
|
69
|
+
logger(chalk.blue('║') + chalk.white.bold(' Hyperdrive Setup Complete ') + chalk.blue('║'));
|
|
70
|
+
logger(chalk.blue('╚═══════════════════════════════════════════════════════════════╝'));
|
|
71
|
+
logger('');
|
|
72
|
+
// Display tenant domain status (always completed)
|
|
73
|
+
logger(` ${chalk.green('✓')} ${formatLabel('Tenant:')} ${chalk.cyan(results.tenantDomain)}`);
|
|
74
|
+
// Display authentication status
|
|
75
|
+
if (results.authCompleted) {
|
|
76
|
+
const authInfo = results.userEmail
|
|
77
|
+
? `Logged in as ${results.userEmail}`
|
|
78
|
+
: 'Complete';
|
|
79
|
+
logger(` ${chalk.green('✓')} ${formatLabel('Authentication:')} ${authInfo}`);
|
|
80
|
+
}
|
|
81
|
+
else if (results.authSkipped) {
|
|
82
|
+
logger(` ${chalk.gray('–')} ${formatLabel('Authentication:')} ${chalk.gray('Skipped (required for most commands)')}`);
|
|
83
|
+
}
|
|
84
|
+
// Display AWS accounts status
|
|
85
|
+
if (results.connectedAccounts.length > 0) {
|
|
86
|
+
const accountCount = results.connectedAccounts.length;
|
|
87
|
+
const accountText = accountCount === 1 ? '1 account connected' : `${accountCount} accounts connected`;
|
|
88
|
+
logger(` ${chalk.green('✓')} ${formatLabel('AWS Accounts:')} ${accountText}`);
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
logger(` ${chalk.gray('–')} ${formatLabel('AWS Accounts:')} ${chalk.gray('None connected')}`);
|
|
92
|
+
}
|
|
93
|
+
// Display Git provider status
|
|
94
|
+
if (results.gitProvider && !results.gitSkipped) {
|
|
95
|
+
const providerName = results.gitProvider === 'github' ? 'GitHub' : 'GitLab';
|
|
96
|
+
const accountInfo = results.gitAccountName ? ` (${results.gitAccountName})` : '';
|
|
97
|
+
logger(` ${chalk.green('✓')} ${formatLabel('Git Provider:')} ${providerName}${accountInfo}`);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
logger(` ${chalk.gray('–')} ${formatLabel('Git Provider:')} ${chalk.gray('Not connected')}`);
|
|
101
|
+
}
|
|
102
|
+
// Display Jira integration status
|
|
103
|
+
if (results.jiraDomain && !results.jiraSkipped) {
|
|
104
|
+
logger(` ${chalk.green('✓')} ${formatLabel('Jira:')} ${results.jiraDomain}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
logger(` ${chalk.gray('–')} ${formatLabel('Jira:')} ${chalk.gray('Not connected')}`);
|
|
108
|
+
}
|
|
109
|
+
logger('');
|
|
110
|
+
// Check if all steps are completed
|
|
111
|
+
const allComplete = results.authCompleted
|
|
112
|
+
&& results.connectedAccounts.length > 0
|
|
113
|
+
&& results.gitProvider
|
|
114
|
+
&& !results.gitSkipped
|
|
115
|
+
&& results.jiraDomain
|
|
116
|
+
&& !results.jiraSkipped;
|
|
117
|
+
// Display success/warning message
|
|
118
|
+
if (allComplete) {
|
|
119
|
+
logger(chalk.green('🎉 Setup complete! You\'re ready to start building.'));
|
|
120
|
+
}
|
|
121
|
+
else if (results.authSkipped) {
|
|
122
|
+
logger(chalk.yellow('⚠️ Authentication is required for most CLI operations.'));
|
|
123
|
+
}
|
|
124
|
+
logger('');
|
|
125
|
+
// Get and display next steps
|
|
126
|
+
const nextSteps = getNextSteps(results);
|
|
127
|
+
logger(chalk.blue('Next Steps:'));
|
|
128
|
+
if (allComplete) {
|
|
129
|
+
// All complete - show module create suggestion
|
|
130
|
+
logger(` Run ${chalk.cyan('`hd module create`')} to create your first serverless module`);
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Show numbered list of recommendations
|
|
134
|
+
nextSteps.forEach((step, index) => {
|
|
135
|
+
logger(` ${index + 1}. Run ${chalk.cyan(`\`${step.command}\``)} - ${step.description}`);
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
logger('');
|
|
139
|
+
logger(chalk.gray('For help with any command, run `hd <command> --help`'));
|
|
140
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate tenant domain format
|
|
3
|
+
*
|
|
4
|
+
* @param domain - The domain string to validate
|
|
5
|
+
* @returns true if domain format is valid, false otherwise
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* validateTenantDomain('acme.hyperdrive.bot') // true
|
|
9
|
+
* validateTenantDomain('my-company.hyperdrive.bot') // true
|
|
10
|
+
* validateTenantDomain('client.example.com') // true
|
|
11
|
+
* validateTenantDomain('subdomain.client.example.io') // true
|
|
12
|
+
* validateTenantDomain('acme') // false (no TLD)
|
|
13
|
+
* validateTenantDomain('.hyperdrive.bot') // false (starts with dot)
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateTenantDomain(domain: string): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Domain validation regex pattern
|
|
3
|
+
*
|
|
4
|
+
* Accepts:
|
|
5
|
+
* - subdomain.hyperdrive.bot (official Hyperdrive domains)
|
|
6
|
+
* - custom domains like client.example.com
|
|
7
|
+
*
|
|
8
|
+
* Pattern breakdown:
|
|
9
|
+
* - ^[a-z0-9-]+\.hyperdrive\.bot$ - Matches hyperdrive.bot subdomains
|
|
10
|
+
* - ^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$ - Matches custom domains with valid TLD
|
|
11
|
+
*/
|
|
12
|
+
const DOMAIN_REGEX = /^[a-z0-9-]+\.hyperdrive\.bot$|^[a-z0-9][a-z0-9.-]*\.[a-z]{2,}$/i;
|
|
13
|
+
/**
|
|
14
|
+
* Validate tenant domain format
|
|
15
|
+
*
|
|
16
|
+
* @param domain - The domain string to validate
|
|
17
|
+
* @returns true if domain format is valid, false otherwise
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* validateTenantDomain('acme.hyperdrive.bot') // true
|
|
21
|
+
* validateTenantDomain('my-company.hyperdrive.bot') // true
|
|
22
|
+
* validateTenantDomain('client.example.com') // true
|
|
23
|
+
* validateTenantDomain('subdomain.client.example.io') // true
|
|
24
|
+
* validateTenantDomain('acme') // false (no TLD)
|
|
25
|
+
* validateTenantDomain('.hyperdrive.bot') // false (starts with dot)
|
|
26
|
+
*/
|
|
27
|
+
export function validateTenantDomain(domain) {
|
|
28
|
+
if (!domain || typeof domain !== 'string') {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return DOMAIN_REGEX.test(domain.trim());
|
|
32
|
+
}
|