@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,375 @@
|
|
|
1
|
+
import { Sha256 } from '@aws-crypto/sha256-js';
|
|
2
|
+
import { HttpRequest } from '@smithy/protocol-http';
|
|
3
|
+
import { SignatureV4 } from '@smithy/signature-v4';
|
|
4
|
+
import axios from 'axios';
|
|
5
|
+
import { readFileSync } from 'fs';
|
|
6
|
+
import { dirname, join } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { AuthService } from './auth-service.js';
|
|
9
|
+
// Get CLI version from package.json
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = dirname(__filename);
|
|
12
|
+
const packageJson = JSON.parse(readFileSync(join(__dirname, '../../package.json'), 'utf-8'));
|
|
13
|
+
const CLI_VERSION = packageJson.version;
|
|
14
|
+
// Type guards
|
|
15
|
+
function isOutdatedCLIError(data) {
|
|
16
|
+
return (data &&
|
|
17
|
+
data.code === 'OUTDATED_CLI_VERSION' &&
|
|
18
|
+
data.data &&
|
|
19
|
+
typeof data.data.currentVersion === 'string' &&
|
|
20
|
+
typeof data.data.requiredVersion === 'string' &&
|
|
21
|
+
typeof data.data.updateCommand === 'string');
|
|
22
|
+
}
|
|
23
|
+
function isOutdatedRoleError(data) {
|
|
24
|
+
return (data &&
|
|
25
|
+
data.code === 'OUTDATED_ROLE_VERSION' &&
|
|
26
|
+
data.data &&
|
|
27
|
+
typeof data.data.accountId === 'string' &&
|
|
28
|
+
typeof data.data.currentVersion === 'string' &&
|
|
29
|
+
typeof data.data.requiredVersion === 'string' &&
|
|
30
|
+
typeof data.data.quickCreateUrl === 'string');
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Hyperdrive API Service with AWS SigV4 authentication
|
|
34
|
+
*
|
|
35
|
+
* This service signs all API requests using AWS Signature Version 4,
|
|
36
|
+
* which allows both CLI and web frontend to use the same endpoints
|
|
37
|
+
* with unified AWS IAM authentication via Cognito.
|
|
38
|
+
*/
|
|
39
|
+
export class HyperdriveSigV4Service {
|
|
40
|
+
apiUrl;
|
|
41
|
+
authService;
|
|
42
|
+
region;
|
|
43
|
+
tenantDomain;
|
|
44
|
+
constructor(domain) {
|
|
45
|
+
this.authService = new AuthService(domain);
|
|
46
|
+
// Load credentials to get API URL and region
|
|
47
|
+
const credentials = this.authService.loadCredentials();
|
|
48
|
+
if (!credentials) {
|
|
49
|
+
const domainMsg = domain ? ` for domain "${domain}"` : '';
|
|
50
|
+
throw new Error(`Not authenticated${domainMsg}. Please run "hd auth login${domain ? ' --domain ' + domain : ''}" first.`);
|
|
51
|
+
}
|
|
52
|
+
// Use environment variable override if set, otherwise use from credentials
|
|
53
|
+
this.apiUrl = process.env.HYPERDRIVE_API_URL || credentials.apiUrl;
|
|
54
|
+
this.region = process.env.HYPERDRIVE_AWS_REGION || credentials.region;
|
|
55
|
+
this.tenantDomain = credentials.tenantDomain;
|
|
56
|
+
if (!this.apiUrl) {
|
|
57
|
+
throw new Error('Hyperdrive API URL not configured.\n' +
|
|
58
|
+
'Please re-authenticate with "hd auth login".');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async accountAdd(params) {
|
|
62
|
+
return this.makeSignedRequest('POST', '/cloud-account/aws/account', params);
|
|
63
|
+
}
|
|
64
|
+
async accountGet(params) {
|
|
65
|
+
return this.makeSignedRequest('GET', `/cloud-account/aws/account/${params.accountId}`);
|
|
66
|
+
}
|
|
67
|
+
// ============================================================================
|
|
68
|
+
// Public API Methods
|
|
69
|
+
// ============================================================================
|
|
70
|
+
async accountList() {
|
|
71
|
+
return this.makeSignedRequest('GET', '/cloud-account/aws/accounts');
|
|
72
|
+
}
|
|
73
|
+
async accountRemove(params) {
|
|
74
|
+
return this.makeSignedRequest('DELETE', `/cloud-account/aws/account/${params.accountId}`);
|
|
75
|
+
}
|
|
76
|
+
async accountVerify(params) {
|
|
77
|
+
return this.makeSignedRequest('POST', `/cloud-account/aws/account/${params.accountId}/verify`);
|
|
78
|
+
}
|
|
79
|
+
async ciAccountCreate(params) {
|
|
80
|
+
return this.makeSignedRequest('POST', '/ci/accounts', params);
|
|
81
|
+
}
|
|
82
|
+
async ciAccountDelete(accountId) {
|
|
83
|
+
return this.makeSignedRequest('DELETE', `/ci/accounts/${accountId}`);
|
|
84
|
+
}
|
|
85
|
+
async ciAccountList() {
|
|
86
|
+
return this.makeSignedRequest('GET', '/ci/accounts');
|
|
87
|
+
}
|
|
88
|
+
async deploymentCheck(params) {
|
|
89
|
+
return this.makeSignedRequest('POST', '/deployments/check', params);
|
|
90
|
+
}
|
|
91
|
+
async deploymentCreate(params) {
|
|
92
|
+
return this.makeSignedRequest('POST', '/deployments', params);
|
|
93
|
+
}
|
|
94
|
+
async deploymentLaunch(params) {
|
|
95
|
+
return this.makeSignedRequest('POST', '/deployments/launch', params);
|
|
96
|
+
}
|
|
97
|
+
async deploymentList(params) {
|
|
98
|
+
return this.makeSignedRequest('GET', `/deployments?projectSlug=${params.projectSlug}&stage=${params.stage}`);
|
|
99
|
+
}
|
|
100
|
+
async gitAuthInitiate(provider) {
|
|
101
|
+
return this.makeSignedRequest('POST', `/git/${provider}/auth/initiate`);
|
|
102
|
+
}
|
|
103
|
+
async gitCheckRepoAccess(params) {
|
|
104
|
+
return this.makeSignedRequest('POST', '/git/repos/check-access', params);
|
|
105
|
+
}
|
|
106
|
+
async gitDeleteInstallation(provider, installationId) {
|
|
107
|
+
return this.makeSignedRequest('DELETE', `/git/installations/${provider}/${installationId}`);
|
|
108
|
+
}
|
|
109
|
+
async gitListInstallations(provider) {
|
|
110
|
+
const query = provider ? `?provider=${provider}` : '';
|
|
111
|
+
return this.makeSignedRequest('GET', `/git/installations${query}`);
|
|
112
|
+
}
|
|
113
|
+
async gitListRepos(params) {
|
|
114
|
+
const queryParams = new URLSearchParams({
|
|
115
|
+
installationId: params.installationId,
|
|
116
|
+
provider: params.provider,
|
|
117
|
+
...(params.page && { page: String(params.page) }),
|
|
118
|
+
...(params.perPage && { perPage: String(params.perPage) }),
|
|
119
|
+
});
|
|
120
|
+
return this.makeSignedRequest('GET', `/git/repos?${queryParams}`);
|
|
121
|
+
}
|
|
122
|
+
async jiraPreRegister(params) {
|
|
123
|
+
return this.makeSignedRequest('POST', '/hyperdrive/jira/pre-register', params);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Test API connection
|
|
127
|
+
*/
|
|
128
|
+
async makeTestRequest() {
|
|
129
|
+
return this.makeSignedRequest('GET', '/');
|
|
130
|
+
}
|
|
131
|
+
async moduleAnalyze(slug) {
|
|
132
|
+
return this.makeSignedRequest('POST', `/modules/${slug}/analyze`);
|
|
133
|
+
}
|
|
134
|
+
async moduleCreate(module) {
|
|
135
|
+
return this.makeSignedRequest('POST', '/modules', module);
|
|
136
|
+
}
|
|
137
|
+
async moduleDestroy(params) {
|
|
138
|
+
return this.makeSignedRequest('DELETE', `/modules/${params.slug}`);
|
|
139
|
+
}
|
|
140
|
+
async moduleGet(params) {
|
|
141
|
+
return this.makeSignedRequest('GET', `/modules/${params.slug}`);
|
|
142
|
+
}
|
|
143
|
+
async moduleGetDockerfile(slug) {
|
|
144
|
+
try {
|
|
145
|
+
return await this.makeSignedRequest('GET', `/modules/${slug}/dockerfile`);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
// Return null if 404 (not found)
|
|
149
|
+
if (error?.response?.status === 404) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// AWS Account Methods
|
|
157
|
+
// ============================================================================
|
|
158
|
+
async moduleLink(params) {
|
|
159
|
+
return this.makeSignedRequest('POST', '/modules/link', params);
|
|
160
|
+
}
|
|
161
|
+
async moduleList() {
|
|
162
|
+
return this.makeSignedRequest('GET', '/modules');
|
|
163
|
+
}
|
|
164
|
+
async moduleReanalyze(slug, userPrompt) {
|
|
165
|
+
return this.makeSignedRequest('POST', `/modules/${slug}/reanalyze`, { userPrompt });
|
|
166
|
+
}
|
|
167
|
+
async moduleUpdate(params) {
|
|
168
|
+
const { slug, ...updateData } = params;
|
|
169
|
+
return this.makeSignedRequest('PUT', `/modules/${slug}`, updateData);
|
|
170
|
+
}
|
|
171
|
+
async parameterAdd(params) {
|
|
172
|
+
return this.makeSignedRequest('POST', '/parameters', params);
|
|
173
|
+
}
|
|
174
|
+
// ============================================================================
|
|
175
|
+
// Git Integration Methods
|
|
176
|
+
// ============================================================================
|
|
177
|
+
async parameterBackfill(params) {
|
|
178
|
+
return this.makeSignedRequest('POST', '/parameters/backfill', params);
|
|
179
|
+
}
|
|
180
|
+
async parameterClear(params) {
|
|
181
|
+
return this.makeSignedRequest('POST', '/parameters/clear', params);
|
|
182
|
+
}
|
|
183
|
+
async parameterList(params) {
|
|
184
|
+
return this.makeSignedRequest('GET', `/parameters?accountId=${params.accountId}&projectSlug=${params.projectSlug}&specific=${params.specific}&stage=${params.stage}`);
|
|
185
|
+
}
|
|
186
|
+
async parameterRemove(params) {
|
|
187
|
+
return this.makeSignedRequest('DELETE', `/parameters/${params.key}?accountId=${params.accountId}&projectSlug=${params.projectSlug}&specific=${params.specific}&stage=${params.stage}`);
|
|
188
|
+
}
|
|
189
|
+
async parameterSync(params) {
|
|
190
|
+
return this.makeSignedRequest('POST', '/parameters/sync', params);
|
|
191
|
+
}
|
|
192
|
+
// ============================================================================
|
|
193
|
+
// Jira Integration Methods
|
|
194
|
+
// ============================================================================
|
|
195
|
+
async parameterSyncStatus(params) {
|
|
196
|
+
return this.makeSignedRequest('GET', `/parameters/sync/${params.taskId}`);
|
|
197
|
+
}
|
|
198
|
+
// ============================================================================
|
|
199
|
+
// CI Account Methods
|
|
200
|
+
// ============================================================================
|
|
201
|
+
async parameterUpdate(params) {
|
|
202
|
+
return this.makeSignedRequest('PUT', `/parameters/${params.key}`, {
|
|
203
|
+
accountId: params.accountId,
|
|
204
|
+
encodedContent: params.encodedContent,
|
|
205
|
+
projectSlug: params.projectSlug,
|
|
206
|
+
specific: params.specific,
|
|
207
|
+
stage: params.stage
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
async stageCreate(params) {
|
|
211
|
+
return this.makeSignedRequest('POST', '/stages', params);
|
|
212
|
+
}
|
|
213
|
+
async stageGet(params) {
|
|
214
|
+
return this.makeSignedRequest('GET', `/stages/${params.name}`);
|
|
215
|
+
}
|
|
216
|
+
// ============================================================================
|
|
217
|
+
// Dockerfile Discovery Methods
|
|
218
|
+
// ============================================================================
|
|
219
|
+
async stageList() {
|
|
220
|
+
return this.makeSignedRequest('GET', '/stages');
|
|
221
|
+
}
|
|
222
|
+
async handleError(error) {
|
|
223
|
+
const axiosError = error;
|
|
224
|
+
if (axiosError.response) {
|
|
225
|
+
const data = axiosError.response.data;
|
|
226
|
+
// Handle outdated CLI version error with type safety
|
|
227
|
+
if (axiosError.response.status === 426 && isOutdatedCLIError(data)) {
|
|
228
|
+
await this.handleOutdatedCLI(data);
|
|
229
|
+
}
|
|
230
|
+
// Handle outdated role version error with type safety
|
|
231
|
+
if (axiosError.response.status === 426 && isOutdatedRoleError(data)) {
|
|
232
|
+
await this.handleOutdatedRole(data);
|
|
233
|
+
}
|
|
234
|
+
console.error('Error response:', axiosError.response.data);
|
|
235
|
+
console.error('Error status:', axiosError.response.status);
|
|
236
|
+
console.error('Error headers:', axiosError.response.headers);
|
|
237
|
+
}
|
|
238
|
+
else if (axiosError.request) {
|
|
239
|
+
console.error('Error request:', axiosError.request);
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
console.error('Error message:', axiosError.message);
|
|
243
|
+
}
|
|
244
|
+
throw axiosError;
|
|
245
|
+
}
|
|
246
|
+
async handleOutdatedCLI(errorData) {
|
|
247
|
+
const { data } = errorData;
|
|
248
|
+
console.error('\n❌ Your Hyperdrive CLI is outdated\n');
|
|
249
|
+
console.error(`Current version: ${data.currentVersion}`);
|
|
250
|
+
console.error(`Required version: ${data.requiredVersion}\n`);
|
|
251
|
+
console.error('To update:');
|
|
252
|
+
console.error(' npm install -g hyperdrive.bot@latest');
|
|
253
|
+
console.error(' # or');
|
|
254
|
+
console.error(' hd update\n');
|
|
255
|
+
// Throw error to be caught by top-level handler
|
|
256
|
+
const error = new Error('CLI_OUTDATED');
|
|
257
|
+
error.code = 'OUTDATED_CLI_VERSION';
|
|
258
|
+
error.data = data;
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
// ============================================================================
|
|
262
|
+
// Helper Methods
|
|
263
|
+
// ============================================================================
|
|
264
|
+
async handleOutdatedRole(errorData) {
|
|
265
|
+
const { data } = errorData;
|
|
266
|
+
const inquirer = (await import('inquirer')).default;
|
|
267
|
+
const open = (await import('open')).default;
|
|
268
|
+
console.error('\n❌ Your cross-account IAM role needs an update\n');
|
|
269
|
+
console.error(`Current version: ${data.currentVersion || 'unknown'}`);
|
|
270
|
+
console.error(`Required version: ${data.requiredVersion}\n`);
|
|
271
|
+
console.error('Update steps:');
|
|
272
|
+
data.instructions?.forEach((step) => console.error(` ${step}`));
|
|
273
|
+
const { openBrowser } = await inquirer.prompt([{
|
|
274
|
+
default: true,
|
|
275
|
+
message: '\nOpen CloudFormation console to update?',
|
|
276
|
+
name: 'openBrowser',
|
|
277
|
+
type: 'confirm'
|
|
278
|
+
}]);
|
|
279
|
+
if (openBrowser) {
|
|
280
|
+
await open(data.quickCreateUrl);
|
|
281
|
+
console.log('\n✓ Browser opened. After updating, retry your command.\n');
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
console.error('\nUpdate URL (copy and paste):');
|
|
285
|
+
console.error(` ${data.quickCreateUrl}\n`);
|
|
286
|
+
}
|
|
287
|
+
// Exit gracefully - user needs to update their role first
|
|
288
|
+
process.exit(1);
|
|
289
|
+
}
|
|
290
|
+
handleResponse(response) {
|
|
291
|
+
return response.data;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Make a signed API request
|
|
295
|
+
*/
|
|
296
|
+
async makeSignedRequest(method, path, data) {
|
|
297
|
+
try {
|
|
298
|
+
// Ensure we have valid credentials (auto-refreshes if needed)
|
|
299
|
+
const credentials = await this.authService.ensureValidCredentials();
|
|
300
|
+
// Sign the request
|
|
301
|
+
const signedRequest = await this.signRequest(method, path, credentials.awsCredentials, data);
|
|
302
|
+
// Convert signed request to axios config
|
|
303
|
+
// Build URL with query string if present
|
|
304
|
+
let url = `${signedRequest.protocol}//${signedRequest.hostname}${signedRequest.path}`;
|
|
305
|
+
if (signedRequest.query && Object.keys(signedRequest.query).length > 0) {
|
|
306
|
+
const queryString = new URLSearchParams(signedRequest.query).toString();
|
|
307
|
+
url += `?${queryString}`;
|
|
308
|
+
}
|
|
309
|
+
const response = await axios({
|
|
310
|
+
data: signedRequest.body,
|
|
311
|
+
headers: signedRequest.headers,
|
|
312
|
+
method: signedRequest.method,
|
|
313
|
+
url,
|
|
314
|
+
});
|
|
315
|
+
return this.handleResponse(response);
|
|
316
|
+
}
|
|
317
|
+
catch (error) {
|
|
318
|
+
return this.handleError(error);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Sign an HTTP request using AWS Signature V4
|
|
323
|
+
*/
|
|
324
|
+
async signRequest(method, path, credentials, body) {
|
|
325
|
+
// Parse API URL
|
|
326
|
+
const url = new URL(this.apiUrl);
|
|
327
|
+
const hostname = url.hostname;
|
|
328
|
+
const protocol = url.protocol.replace(':', '');
|
|
329
|
+
const basePath = url.pathname.replace(/\/$/, ''); // Remove trailing slash
|
|
330
|
+
// Parse query string from path if present
|
|
331
|
+
let pathWithoutQuery = path;
|
|
332
|
+
let query;
|
|
333
|
+
const queryIndex = path.indexOf('?');
|
|
334
|
+
if (queryIndex !== -1) {
|
|
335
|
+
pathWithoutQuery = path.substring(0, queryIndex);
|
|
336
|
+
const queryString = path.substring(queryIndex + 1);
|
|
337
|
+
query = {};
|
|
338
|
+
const params = new URLSearchParams(queryString);
|
|
339
|
+
for (const [key, value] of params) {
|
|
340
|
+
query[key] = value;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
// Combine base path with request path (without query string)
|
|
344
|
+
const fullPath = basePath + pathWithoutQuery;
|
|
345
|
+
// Create signer
|
|
346
|
+
const signer = new SignatureV4({
|
|
347
|
+
credentials: {
|
|
348
|
+
accessKeyId: credentials.accessKeyId,
|
|
349
|
+
secretAccessKey: credentials.secretAccessKey,
|
|
350
|
+
sessionToken: credentials.sessionToken,
|
|
351
|
+
},
|
|
352
|
+
region: this.region,
|
|
353
|
+
service: 'execute-api',
|
|
354
|
+
sha256: Sha256,
|
|
355
|
+
});
|
|
356
|
+
// Prepare request
|
|
357
|
+
const request = new HttpRequest({
|
|
358
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
359
|
+
headers: {
|
|
360
|
+
'Content-Type': 'application/json',
|
|
361
|
+
'User-Agent': `hyperdrive-cli/${CLI_VERSION}`,
|
|
362
|
+
'X-Tenant-Domain': this.tenantDomain,
|
|
363
|
+
host: hostname,
|
|
364
|
+
},
|
|
365
|
+
hostname,
|
|
366
|
+
method: method.toUpperCase(),
|
|
367
|
+
path: fullPath,
|
|
368
|
+
protocol,
|
|
369
|
+
query,
|
|
370
|
+
});
|
|
371
|
+
// Sign the request
|
|
372
|
+
const signedRequest = await signer.sign(request);
|
|
373
|
+
return signedRequest;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
interface DeploymentCheckResponse {
|
|
2
|
+
[key: string]: unknown;
|
|
3
|
+
status: string;
|
|
4
|
+
}
|
|
5
|
+
interface DeploymentResponse {
|
|
6
|
+
[key: string]: unknown;
|
|
7
|
+
}
|
|
8
|
+
interface ModuleResponse {
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
interface ParameterSyncResponse {
|
|
12
|
+
[key: string]: unknown;
|
|
13
|
+
errors?: string[];
|
|
14
|
+
}
|
|
15
|
+
interface StageResponse {
|
|
16
|
+
[key: string]: unknown;
|
|
17
|
+
}
|
|
18
|
+
export declare class HyperdriveService {
|
|
19
|
+
deploymentCheck({ name, projectSlug, stage, token }: {
|
|
20
|
+
name: string;
|
|
21
|
+
projectSlug: string;
|
|
22
|
+
stage: string;
|
|
23
|
+
token: string;
|
|
24
|
+
}): Promise<DeploymentCheckResponse>;
|
|
25
|
+
deploymentCreate({ commit, launch, name, projectSlug, regions, stage, token }: {
|
|
26
|
+
commit: string;
|
|
27
|
+
launch: boolean;
|
|
28
|
+
name: string;
|
|
29
|
+
projectSlug: string;
|
|
30
|
+
regions: string[];
|
|
31
|
+
stage: string;
|
|
32
|
+
token: string;
|
|
33
|
+
}): Promise<DeploymentResponse>;
|
|
34
|
+
deploymentLaunch({ name, projectSlug, region, stage, token }: {
|
|
35
|
+
name: string;
|
|
36
|
+
projectSlug: string;
|
|
37
|
+
region?: string;
|
|
38
|
+
stage: string;
|
|
39
|
+
token: string;
|
|
40
|
+
}): Promise<DeploymentResponse>;
|
|
41
|
+
moduleCreate(module: {
|
|
42
|
+
buildCommand: string;
|
|
43
|
+
buildFolder: string;
|
|
44
|
+
ciService: string;
|
|
45
|
+
framework: string;
|
|
46
|
+
installCommand: string;
|
|
47
|
+
name: string;
|
|
48
|
+
runCommand: string;
|
|
49
|
+
slug: string;
|
|
50
|
+
sourceLocation: string;
|
|
51
|
+
token: string;
|
|
52
|
+
}): Promise<ModuleResponse>;
|
|
53
|
+
moduleLink({ originSlug, parameter: parameters, targetSlug, token }: {
|
|
54
|
+
originSlug: string;
|
|
55
|
+
parameter: string[];
|
|
56
|
+
targetSlug: string;
|
|
57
|
+
token: string;
|
|
58
|
+
}): Promise<ModuleResponse>;
|
|
59
|
+
parameterSync({ accountId, encodedContent, projectSlug, specific, stage, token }: {
|
|
60
|
+
accountId: string;
|
|
61
|
+
encodedContent: string;
|
|
62
|
+
projectSlug?: string;
|
|
63
|
+
specific: boolean;
|
|
64
|
+
stage?: string;
|
|
65
|
+
token: string;
|
|
66
|
+
}): Promise<ParameterSyncResponse>;
|
|
67
|
+
stageCreate({ accountId, branchName, defaultStage, deletionProtection, name, production, project: projects, provider, region: regions, token }: {
|
|
68
|
+
accountId?: string;
|
|
69
|
+
branchName: string;
|
|
70
|
+
defaultStage: boolean;
|
|
71
|
+
deletionProtection: boolean;
|
|
72
|
+
name: string;
|
|
73
|
+
production: boolean;
|
|
74
|
+
project: string[];
|
|
75
|
+
provider?: string;
|
|
76
|
+
region: string[];
|
|
77
|
+
token: string;
|
|
78
|
+
}): Promise<StageResponse>;
|
|
79
|
+
stageGet({ name, token }: {
|
|
80
|
+
name: string;
|
|
81
|
+
token: string;
|
|
82
|
+
}): Promise<StageResponse>;
|
|
83
|
+
private handleError;
|
|
84
|
+
private handleRequest;
|
|
85
|
+
private handleResponse;
|
|
86
|
+
}
|
|
87
|
+
export {};
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
export class HyperdriveService {
|
|
3
|
+
async deploymentCheck({ name, projectSlug, stage, token }) {
|
|
4
|
+
return this.handleRequest({
|
|
5
|
+
data: { name, projectSlug, stage },
|
|
6
|
+
method: 'post',
|
|
7
|
+
token,
|
|
8
|
+
url: `${process.env.HYPERDRIVE_API_URL}/deployment/check`,
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
async deploymentCreate({ commit, launch, name, projectSlug, regions, stage, token }) {
|
|
12
|
+
return this.handleRequest({
|
|
13
|
+
data: {
|
|
14
|
+
commit, launch, name, projectSlug, regions, stage,
|
|
15
|
+
},
|
|
16
|
+
method: 'post',
|
|
17
|
+
token,
|
|
18
|
+
url: `${process.env.HYPERDRIVE_API_URL}/deployment`,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
async deploymentLaunch({ name, projectSlug, region, stage, token }) {
|
|
22
|
+
return this.handleRequest({
|
|
23
|
+
data: {
|
|
24
|
+
name, projectSlug, region, stage,
|
|
25
|
+
},
|
|
26
|
+
method: 'post',
|
|
27
|
+
token,
|
|
28
|
+
url: `${process.env.HYPERDRIVE_API_URL}/deployment/${name}/launch`,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
async moduleCreate(module) {
|
|
32
|
+
return this.handleRequest({
|
|
33
|
+
data: module,
|
|
34
|
+
method: 'post',
|
|
35
|
+
token: module.token,
|
|
36
|
+
url: `${process.env.HYPERDRIVE_API_URL}/module`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
async moduleLink({ originSlug, parameter: parameters, targetSlug, token }) {
|
|
40
|
+
return this.handleRequest({
|
|
41
|
+
data: { originSlug, parameters, targetSlug },
|
|
42
|
+
method: 'post',
|
|
43
|
+
token,
|
|
44
|
+
url: `${process.env.HYPERDRIVE_API_URL}/module/link`,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async parameterSync({ accountId, encodedContent, projectSlug, specific, stage, token }) {
|
|
48
|
+
return this.handleRequest({
|
|
49
|
+
data: {
|
|
50
|
+
accountId, encodedContent, projectSlug, specific, stage,
|
|
51
|
+
},
|
|
52
|
+
method: 'post',
|
|
53
|
+
token,
|
|
54
|
+
url: `${process.env.HYPERDRIVE_API_URL}/parameter/sync`,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
async stageCreate({ accountId, branchName, defaultStage, deletionProtection, name, production, project: projects, provider, region: regions, token }) {
|
|
58
|
+
return this.handleRequest({
|
|
59
|
+
data: {
|
|
60
|
+
accountId, branchName, defaultStage, deletionProtection, name, production, projects, provider, regions,
|
|
61
|
+
},
|
|
62
|
+
method: 'post',
|
|
63
|
+
token,
|
|
64
|
+
url: `${process.env.HYPERDRIVE_API_URL}/stage`,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
async stageGet({ name, token }) {
|
|
68
|
+
return this.handleRequest({
|
|
69
|
+
method: 'get',
|
|
70
|
+
token,
|
|
71
|
+
url: `${process.env.HYPERDRIVE_API_URL}/stage/${name}`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
handleError(error) {
|
|
75
|
+
const axiosError = error;
|
|
76
|
+
if (axiosError.response) {
|
|
77
|
+
console.error('Error response:', axiosError.response.data);
|
|
78
|
+
console.error('Error status:', axiosError.response.status);
|
|
79
|
+
console.error('Error headers:', axiosError.response.headers);
|
|
80
|
+
}
|
|
81
|
+
else if (axiosError.request) {
|
|
82
|
+
console.error('Error request:', axiosError.request);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.error('Error message:', axiosError.message);
|
|
86
|
+
}
|
|
87
|
+
throw axiosError;
|
|
88
|
+
}
|
|
89
|
+
async handleRequest({ data, method, token, url }) {
|
|
90
|
+
try {
|
|
91
|
+
const response = await axios({
|
|
92
|
+
data: method === 'post' ? data : undefined,
|
|
93
|
+
headers: {
|
|
94
|
+
Authorization: `Bearer ${token}`,
|
|
95
|
+
},
|
|
96
|
+
method,
|
|
97
|
+
url,
|
|
98
|
+
});
|
|
99
|
+
return this.handleResponse(response);
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return this.handleError(error);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
handleResponse(response) {
|
|
106
|
+
return response.data;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CloudWatch Log Tailer Service
|
|
3
|
+
*
|
|
4
|
+
* Provides real-time log streaming from CloudWatch for deployment feedback.
|
|
5
|
+
* Uses scoped temporary credentials provided by the API for least-privilege access.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Scoped AWS credentials returned by the API
|
|
9
|
+
*/
|
|
10
|
+
export interface ScopedCredentials {
|
|
11
|
+
accessKeyId: string;
|
|
12
|
+
expiration: string;
|
|
13
|
+
secretAccessKey: string;
|
|
14
|
+
sessionToken: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Logging configuration returned by the API
|
|
18
|
+
*/
|
|
19
|
+
export interface LoggingConfig {
|
|
20
|
+
credentials: ScopedCredentials;
|
|
21
|
+
logGroup: string;
|
|
22
|
+
logStream: string;
|
|
23
|
+
region: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Tailer options
|
|
27
|
+
*/
|
|
28
|
+
export interface TailerOptions {
|
|
29
|
+
pollInterval?: number;
|
|
30
|
+
showDebug?: boolean;
|
|
31
|
+
verbose?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Tailer result
|
|
35
|
+
*/
|
|
36
|
+
export interface TailerResult {
|
|
37
|
+
details?: Record<string, unknown>;
|
|
38
|
+
message: string;
|
|
39
|
+
success: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* CloudWatch Log Tailer
|
|
43
|
+
*
|
|
44
|
+
* Streams deployment logs in real-time and displays progress to the user.
|
|
45
|
+
*/
|
|
46
|
+
export declare class CloudWatchLogTailer {
|
|
47
|
+
private client;
|
|
48
|
+
private config;
|
|
49
|
+
private currentStage;
|
|
50
|
+
private isComplete;
|
|
51
|
+
private lastEventTime;
|
|
52
|
+
private options;
|
|
53
|
+
private spinner;
|
|
54
|
+
constructor(config: LoggingConfig, options?: TailerOptions);
|
|
55
|
+
/**
|
|
56
|
+
* Start tailing logs until deployment completes or fails
|
|
57
|
+
*/
|
|
58
|
+
tail(): Promise<TailerResult>;
|
|
59
|
+
/**
|
|
60
|
+
* Fetch new events from CloudWatch
|
|
61
|
+
*/
|
|
62
|
+
private fetchNewEvents;
|
|
63
|
+
/**
|
|
64
|
+
* Format duration in human-readable form
|
|
65
|
+
*/
|
|
66
|
+
private formatDuration;
|
|
67
|
+
/**
|
|
68
|
+
* Get color function for log level
|
|
69
|
+
*/
|
|
70
|
+
private getLevelColor;
|
|
71
|
+
/**
|
|
72
|
+
* Handle a log line event
|
|
73
|
+
*/
|
|
74
|
+
private handleLogEvent;
|
|
75
|
+
/**
|
|
76
|
+
* Handle a stage transition event
|
|
77
|
+
*/
|
|
78
|
+
private handleStageEvent;
|
|
79
|
+
/**
|
|
80
|
+
* Check if credentials are still valid
|
|
81
|
+
*/
|
|
82
|
+
private isCredentialsValid;
|
|
83
|
+
/**
|
|
84
|
+
* Process a single event
|
|
85
|
+
*/
|
|
86
|
+
private processEvent;
|
|
87
|
+
/**
|
|
88
|
+
* Render a progress bar
|
|
89
|
+
*/
|
|
90
|
+
private renderProgressBar;
|
|
91
|
+
/**
|
|
92
|
+
* Sleep for specified milliseconds
|
|
93
|
+
*/
|
|
94
|
+
private sleep;
|
|
95
|
+
}
|