@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.
Files changed (127) hide show
  1. package/README.md +1598 -0
  2. package/bin/dev.cmd +3 -0
  3. package/bin/dev.js +3 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +5 -0
  6. package/dist/commands/account/add.d.ts +16 -0
  7. package/dist/commands/account/add.js +185 -0
  8. package/dist/commands/account/list.d.ts +6 -0
  9. package/dist/commands/account/list.js +37 -0
  10. package/dist/commands/account/remove.d.ts +11 -0
  11. package/dist/commands/account/remove.js +57 -0
  12. package/dist/commands/auth/login.d.ts +16 -0
  13. package/dist/commands/auth/login.js +178 -0
  14. package/dist/commands/auth/logout.d.ts +6 -0
  15. package/dist/commands/auth/logout.js +39 -0
  16. package/dist/commands/auth/refresh.d.ts +6 -0
  17. package/dist/commands/auth/refresh.js +66 -0
  18. package/dist/commands/auth/status.d.ts +6 -0
  19. package/dist/commands/auth/status.js +63 -0
  20. package/dist/commands/ci/account/create.d.ts +16 -0
  21. package/dist/commands/ci/account/create.js +158 -0
  22. package/dist/commands/ci/account/delete.d.ts +14 -0
  23. package/dist/commands/ci/account/delete.js +88 -0
  24. package/dist/commands/ci/account/list.d.ts +10 -0
  25. package/dist/commands/ci/account/list.js +65 -0
  26. package/dist/commands/config/get.d.ts +9 -0
  27. package/dist/commands/config/get.js +37 -0
  28. package/dist/commands/config/set.d.ts +10 -0
  29. package/dist/commands/config/set.js +48 -0
  30. package/dist/commands/config/show.d.ts +6 -0
  31. package/dist/commands/config/show.js +10 -0
  32. package/dist/commands/deployment/create.d.ts +30 -0
  33. package/dist/commands/deployment/create.js +188 -0
  34. package/dist/commands/deployment/get.d.ts +13 -0
  35. package/dist/commands/deployment/get.js +101 -0
  36. package/dist/commands/deployment/launch.d.ts +15 -0
  37. package/dist/commands/deployment/launch.js +105 -0
  38. package/dist/commands/deployment/list.d.ts +11 -0
  39. package/dist/commands/deployment/list.js +91 -0
  40. package/dist/commands/domain/current.d.ts +6 -0
  41. package/dist/commands/domain/current.js +18 -0
  42. package/dist/commands/domain/list.d.ts +6 -0
  43. package/dist/commands/domain/list.js +42 -0
  44. package/dist/commands/domain/switch.d.ts +9 -0
  45. package/dist/commands/domain/switch.js +40 -0
  46. package/dist/commands/example.d.ts +13 -0
  47. package/dist/commands/example.js +24 -0
  48. package/dist/commands/git/connect.d.ts +10 -0
  49. package/dist/commands/git/connect.js +56 -0
  50. package/dist/commands/git/disconnect.d.ts +11 -0
  51. package/dist/commands/git/disconnect.js +93 -0
  52. package/dist/commands/git/list.d.ts +10 -0
  53. package/dist/commands/git/list.js +53 -0
  54. package/dist/commands/git/sync.d.ts +18 -0
  55. package/dist/commands/git/sync.js +235 -0
  56. package/dist/commands/init.d.ts +188 -0
  57. package/dist/commands/init.js +817 -0
  58. package/dist/commands/jira/connect.d.ts +9 -0
  59. package/dist/commands/jira/connect.js +141 -0
  60. package/dist/commands/jira/status.d.ts +9 -0
  61. package/dist/commands/jira/status.js +118 -0
  62. package/dist/commands/module/analyze.d.ts +29 -0
  63. package/dist/commands/module/analyze.js +201 -0
  64. package/dist/commands/module/create.d.ts +42 -0
  65. package/dist/commands/module/create.js +498 -0
  66. package/dist/commands/module/destroy.d.ts +11 -0
  67. package/dist/commands/module/destroy.js +77 -0
  68. package/dist/commands/module/get.d.ts +10 -0
  69. package/dist/commands/module/get.js +43 -0
  70. package/dist/commands/module/link.d.ts +15 -0
  71. package/dist/commands/module/link.js +175 -0
  72. package/dist/commands/module/list.d.ts +9 -0
  73. package/dist/commands/module/list.js +51 -0
  74. package/dist/commands/module/reanalyze.d.ts +30 -0
  75. package/dist/commands/module/reanalyze.js +206 -0
  76. package/dist/commands/module/update.d.ts +27 -0
  77. package/dist/commands/module/update.js +102 -0
  78. package/dist/commands/parameter/add.d.ts +15 -0
  79. package/dist/commands/parameter/add.js +99 -0
  80. package/dist/commands/parameter/backfill.d.ts +12 -0
  81. package/dist/commands/parameter/backfill.js +113 -0
  82. package/dist/commands/parameter/clear.d.ts +14 -0
  83. package/dist/commands/parameter/clear.js +95 -0
  84. package/dist/commands/parameter/list.d.ts +14 -0
  85. package/dist/commands/parameter/list.js +92 -0
  86. package/dist/commands/parameter/pull.d.ts +14 -0
  87. package/dist/commands/parameter/pull.js +124 -0
  88. package/dist/commands/parameter/remove.d.ts +15 -0
  89. package/dist/commands/parameter/remove.js +90 -0
  90. package/dist/commands/parameter/sync.d.ts +14 -0
  91. package/dist/commands/parameter/sync.js +153 -0
  92. package/dist/commands/parameter/update.d.ts +15 -0
  93. package/dist/commands/parameter/update.js +100 -0
  94. package/dist/commands/stage/create.d.ts +28 -0
  95. package/dist/commands/stage/create.js +312 -0
  96. package/dist/commands/stage/list.d.ts +9 -0
  97. package/dist/commands/stage/list.js +63 -0
  98. package/dist/commands/test-api.d.ts +9 -0
  99. package/dist/commands/test-api.js +40 -0
  100. package/dist/index.d.ts +1 -0
  101. package/dist/index.js +1 -0
  102. package/dist/services/auth-service.d.ts +84 -0
  103. package/dist/services/auth-service.js +240 -0
  104. package/dist/services/git.d.ts +46 -0
  105. package/dist/services/git.js +409 -0
  106. package/dist/services/hyperdrive-sigv4.d.ts +449 -0
  107. package/dist/services/hyperdrive-sigv4.js +375 -0
  108. package/dist/services/hyperdrive.d.ts +87 -0
  109. package/dist/services/hyperdrive.js +108 -0
  110. package/dist/services/log-tailer.d.ts +95 -0
  111. package/dist/services/log-tailer.js +242 -0
  112. package/dist/services/tenant-service.d.ts +106 -0
  113. package/dist/services/tenant-service.js +332 -0
  114. package/dist/utils/account-flow.d.ts +74 -0
  115. package/dist/utils/account-flow.js +228 -0
  116. package/dist/utils/auth-flow.d.ts +146 -0
  117. package/dist/utils/auth-flow.js +477 -0
  118. package/dist/utils/git-flow.d.ts +72 -0
  119. package/dist/utils/git-flow.js +232 -0
  120. package/dist/utils/jira-flow.d.ts +71 -0
  121. package/dist/utils/jira-flow.js +120 -0
  122. package/dist/utils/summary-display.d.ts +59 -0
  123. package/dist/utils/summary-display.js +140 -0
  124. package/dist/utils/validation.d.ts +15 -0
  125. package/dist/utils/validation.js +32 -0
  126. package/oclif.manifest.json +2819 -0
  127. 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
+ }