@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,312 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
6
|
+
// Full AWS regions list
|
|
7
|
+
const AWS_REGIONS = [
|
|
8
|
+
{ name: 'US East (N. Virginia)', value: 'us-east-1' },
|
|
9
|
+
{ name: 'US East (Ohio)', value: 'us-east-2' },
|
|
10
|
+
{ name: 'US West (N. California)', value: 'us-west-1' },
|
|
11
|
+
{ name: 'US West (Oregon)', value: 'us-west-2' },
|
|
12
|
+
{ name: 'Africa (Cape Town)', value: 'af-south-1' },
|
|
13
|
+
{ name: 'Asia Pacific (Hong Kong)', value: 'ap-east-1' },
|
|
14
|
+
{ name: 'Asia Pacific (Hyderabad)', value: 'ap-south-2' },
|
|
15
|
+
{ name: 'Asia Pacific (Jakarta)', value: 'ap-southeast-3' },
|
|
16
|
+
{ name: 'Asia Pacific (Melbourne)', value: 'ap-southeast-4' },
|
|
17
|
+
{ name: 'Asia Pacific (Mumbai)', value: 'ap-south-1' },
|
|
18
|
+
{ name: 'Asia Pacific (Osaka)', value: 'ap-northeast-3' },
|
|
19
|
+
{ name: 'Asia Pacific (Seoul)', value: 'ap-northeast-2' },
|
|
20
|
+
{ name: 'Asia Pacific (Singapore)', value: 'ap-southeast-1' },
|
|
21
|
+
{ name: 'Asia Pacific (Sydney)', value: 'ap-southeast-2' },
|
|
22
|
+
{ name: 'Asia Pacific (Tokyo)', value: 'ap-northeast-1' },
|
|
23
|
+
{ name: 'Canada (Central)', value: 'ca-central-1' },
|
|
24
|
+
{ name: 'Canada West (Calgary)', value: 'ca-west-1' },
|
|
25
|
+
{ name: 'Europe (Frankfurt)', value: 'eu-central-1' },
|
|
26
|
+
{ name: 'Europe (Ireland)', value: 'eu-west-1' },
|
|
27
|
+
{ name: 'Europe (London)', value: 'eu-west-2' },
|
|
28
|
+
{ name: 'Europe (Milan)', value: 'eu-south-1' },
|
|
29
|
+
{ name: 'Europe (Paris)', value: 'eu-west-3' },
|
|
30
|
+
{ name: 'Europe (Spain)', value: 'eu-south-2' },
|
|
31
|
+
{ name: 'Europe (Stockholm)', value: 'eu-north-1' },
|
|
32
|
+
{ name: 'Europe (Zurich)', value: 'eu-central-2' },
|
|
33
|
+
{ name: 'Israel (Tel Aviv)', value: 'il-central-1' },
|
|
34
|
+
{ name: 'Middle East (Bahrain)', value: 'me-south-1' },
|
|
35
|
+
{ name: 'Middle East (UAE)', value: 'me-central-1' },
|
|
36
|
+
{ name: 'South America (São Paulo)', value: 'sa-east-1' },
|
|
37
|
+
];
|
|
38
|
+
import { CloudWatchLogTailer } from '../../services/log-tailer.js';
|
|
39
|
+
export default class StageCreate extends Command {
|
|
40
|
+
static description = 'Create a new stage';
|
|
41
|
+
static flags = {
|
|
42
|
+
accountId: Flags.string({
|
|
43
|
+
char: 'a',
|
|
44
|
+
description: 'AWS Account ID for deployments',
|
|
45
|
+
}),
|
|
46
|
+
autoLaunch: Flags.boolean({
|
|
47
|
+
default: false,
|
|
48
|
+
description: 'Automatically launch deployments for this stage',
|
|
49
|
+
}),
|
|
50
|
+
branchName: Flags.string({
|
|
51
|
+
char: 'b',
|
|
52
|
+
description: 'Git branch name',
|
|
53
|
+
}),
|
|
54
|
+
defaultStage: Flags.boolean({
|
|
55
|
+
default: false,
|
|
56
|
+
description: 'Set as default stage for fallback',
|
|
57
|
+
}),
|
|
58
|
+
deletionProtection: Flags.boolean({
|
|
59
|
+
default: false,
|
|
60
|
+
description: 'Enable deletion protection',
|
|
61
|
+
}),
|
|
62
|
+
domain: Flags.string({
|
|
63
|
+
char: 'd',
|
|
64
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
65
|
+
}),
|
|
66
|
+
name: Flags.string({
|
|
67
|
+
char: 'n',
|
|
68
|
+
description: 'Name of the stage (e.g., dev, staging, production)',
|
|
69
|
+
}),
|
|
70
|
+
production: Flags.boolean({
|
|
71
|
+
default: false,
|
|
72
|
+
description: 'Mark as production stage',
|
|
73
|
+
}),
|
|
74
|
+
project: Flags.string({
|
|
75
|
+
char: 'p',
|
|
76
|
+
description: 'Project slugs to associate with this stage',
|
|
77
|
+
multiple: true,
|
|
78
|
+
}),
|
|
79
|
+
provider: Flags.string({
|
|
80
|
+
default: 'aws',
|
|
81
|
+
description: 'Cloud provider',
|
|
82
|
+
}),
|
|
83
|
+
region: Flags.string({
|
|
84
|
+
char: 'r',
|
|
85
|
+
description: 'AWS region(s) for deployment',
|
|
86
|
+
multiple: true,
|
|
87
|
+
}),
|
|
88
|
+
};
|
|
89
|
+
async run() {
|
|
90
|
+
const { flags } = await this.parse(StageCreate);
|
|
91
|
+
this.log(chalk.green('🚀 Let\'s create a new stage!'));
|
|
92
|
+
try {
|
|
93
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
94
|
+
const responses = await this.promptUser(flags, service);
|
|
95
|
+
const combinedData = { ...flags, ...responses };
|
|
96
|
+
this.log('');
|
|
97
|
+
this.log(chalk.blue('Creating stage with:'));
|
|
98
|
+
this.log(chalk.gray(` Name: ${combinedData.name}`));
|
|
99
|
+
this.log(chalk.gray(` Account: ${combinedData.accountId}`));
|
|
100
|
+
this.log(chalk.gray(` Region(s): ${combinedData.region.join(', ')}`));
|
|
101
|
+
this.log(chalk.gray(` Branch: ${combinedData.branchName}`));
|
|
102
|
+
this.log(chalk.gray(` Projects: ${combinedData.project.join(', ')}`));
|
|
103
|
+
const result = await service.stageCreate({
|
|
104
|
+
accountId: combinedData.accountId,
|
|
105
|
+
autoLaunch: combinedData.autoLaunch,
|
|
106
|
+
branchName: combinedData.branchName,
|
|
107
|
+
defaultStage: combinedData.defaultStage,
|
|
108
|
+
deletionProtection: combinedData.deletionProtection,
|
|
109
|
+
name: combinedData.name,
|
|
110
|
+
production: combinedData.production,
|
|
111
|
+
projects: combinedData.project,
|
|
112
|
+
provider: combinedData.provider,
|
|
113
|
+
regions: combinedData.region,
|
|
114
|
+
});
|
|
115
|
+
// Check if mission was started (autoLaunch + logging config)
|
|
116
|
+
const loggingConfig = result.logging;
|
|
117
|
+
const missionId = result.missionId;
|
|
118
|
+
if (combinedData.autoLaunch && loggingConfig && missionId) {
|
|
119
|
+
// Start real-time log streaming for mission
|
|
120
|
+
await this.streamMissionLogs(loggingConfig, missionId);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// Standard stage creation (no autoLaunch)
|
|
124
|
+
this.log(chalk.blue('\n✅ Stage created successfully!'));
|
|
125
|
+
this.log(JSON.stringify(result, null, 2));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
const err = error;
|
|
130
|
+
// Ignore EEXIT with code 0 - this is oclif's way of exiting cleanly
|
|
131
|
+
if (err.message?.includes('EEXIT') && err.oclif?.exit === 0) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const axiosError = error;
|
|
135
|
+
const errorMessage = axiosError.response?.data?.message ?? axiosError.message ?? 'Unknown error';
|
|
136
|
+
this.log(chalk.red('❌ Error creating stage: ' + errorMessage));
|
|
137
|
+
this.exit(1);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Build CloudWatch console URL
|
|
142
|
+
*/
|
|
143
|
+
buildConsoleUrl(config) {
|
|
144
|
+
const encodedGroup = encodeURIComponent(encodeURIComponent(config.logGroup));
|
|
145
|
+
const encodedStream = encodeURIComponent(encodeURIComponent(config.logStream));
|
|
146
|
+
return `https://${config.region}.console.aws.amazon.com/cloudwatch/home?region=${config.region}#logsV2:log-groups/log-group/${encodedGroup}/log-events/${encodedStream}`;
|
|
147
|
+
}
|
|
148
|
+
getGitBranch() {
|
|
149
|
+
try {
|
|
150
|
+
return execSync('git branch --show-current', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
async promptUser(flags, service) {
|
|
157
|
+
const responses = {};
|
|
158
|
+
// Stage name
|
|
159
|
+
if (!flags.name) {
|
|
160
|
+
const { name } = await inquirer.prompt([{
|
|
161
|
+
message: chalk.yellow('📛 Enter the stage name (e.g., dev, staging, production):'),
|
|
162
|
+
name: 'name',
|
|
163
|
+
validate: (input) => Boolean(input.trim()) || 'Stage name is required.',
|
|
164
|
+
}]);
|
|
165
|
+
responses.name = name;
|
|
166
|
+
}
|
|
167
|
+
// Branch name (auto-detect from git)
|
|
168
|
+
if (!flags.branchName) {
|
|
169
|
+
const defaultBranch = this.getGitBranch();
|
|
170
|
+
const { branchName } = await inquirer.prompt([{
|
|
171
|
+
default: defaultBranch,
|
|
172
|
+
message: chalk.yellow('🌿 Git branch for this stage:'),
|
|
173
|
+
name: 'branchName',
|
|
174
|
+
validate: (input) => Boolean(input.trim()) || 'Branch name is required.',
|
|
175
|
+
}]);
|
|
176
|
+
responses.branchName = branchName;
|
|
177
|
+
}
|
|
178
|
+
// AWS Account (fetch from API)
|
|
179
|
+
if (!flags.accountId) {
|
|
180
|
+
this.log(chalk.gray('\n Fetching AWS accounts...'));
|
|
181
|
+
const accounts = await service.accountList();
|
|
182
|
+
if (accounts.length === 0) {
|
|
183
|
+
this.log(chalk.red('❌ No AWS accounts found. Please add an account first with: hd account add'));
|
|
184
|
+
this.exit(1);
|
|
185
|
+
}
|
|
186
|
+
const accountChoices = accounts.map(acc => ({
|
|
187
|
+
defaultRegion: acc.defaultRegion,
|
|
188
|
+
name: acc.name ? `${acc.name} (${acc.accountId}) - ${acc.defaultRegion}` : `${acc.accountId} - ${acc.defaultRegion}`,
|
|
189
|
+
value: acc.accountId,
|
|
190
|
+
}));
|
|
191
|
+
const { accountId } = await inquirer.prompt([{
|
|
192
|
+
choices: accountChoices,
|
|
193
|
+
message: chalk.yellow('☁️ Select AWS Account:'),
|
|
194
|
+
name: 'accountId',
|
|
195
|
+
type: 'list',
|
|
196
|
+
}]);
|
|
197
|
+
responses.accountId = accountId;
|
|
198
|
+
// Store default region for pre-selection
|
|
199
|
+
const selectedAccount = accountChoices.find(a => a.value === accountId);
|
|
200
|
+
responses._defaultRegion = selectedAccount?.defaultRegion;
|
|
201
|
+
}
|
|
202
|
+
// Regions (checkbox with full AWS list)
|
|
203
|
+
if (!flags.region || flags.region.length === 0) {
|
|
204
|
+
const defaultRegion = responses._defaultRegion;
|
|
205
|
+
// Pre-select the account's default region if available
|
|
206
|
+
const regionChoices = AWS_REGIONS.map(r => ({
|
|
207
|
+
checked: r.value === defaultRegion,
|
|
208
|
+
name: `${r.name} (${r.value})`,
|
|
209
|
+
value: r.value,
|
|
210
|
+
}));
|
|
211
|
+
const { region } = await inquirer.prompt([{
|
|
212
|
+
choices: regionChoices,
|
|
213
|
+
message: chalk.yellow('🌍 Select deployment region(s):'),
|
|
214
|
+
name: 'region',
|
|
215
|
+
type: 'checkbox',
|
|
216
|
+
validate: (input) => input.length > 0 || 'At least one region is required.',
|
|
217
|
+
}]);
|
|
218
|
+
responses.region = region;
|
|
219
|
+
}
|
|
220
|
+
// Projects (fetch from API, multi-select)
|
|
221
|
+
if (!flags.project || flags.project.length === 0) {
|
|
222
|
+
this.log(chalk.gray('\n Fetching projects...'));
|
|
223
|
+
const modules = await service.moduleList();
|
|
224
|
+
if (modules.length === 0) {
|
|
225
|
+
this.log(chalk.red('❌ No projects found. Please create a project first with: hd module create'));
|
|
226
|
+
this.exit(1);
|
|
227
|
+
}
|
|
228
|
+
const projectChoices = modules.map(m => ({
|
|
229
|
+
name: m.name ? `${m.name} (${m.slug})` : m.slug,
|
|
230
|
+
value: m.slug,
|
|
231
|
+
}));
|
|
232
|
+
const { project } = await inquirer.prompt([{
|
|
233
|
+
choices: projectChoices,
|
|
234
|
+
message: chalk.yellow('📦 Select projects to include in this stage:'),
|
|
235
|
+
name: 'project',
|
|
236
|
+
type: 'checkbox',
|
|
237
|
+
validate: (input) => input.length > 0 || 'At least one project is required.',
|
|
238
|
+
}]);
|
|
239
|
+
responses.project = project;
|
|
240
|
+
}
|
|
241
|
+
// Additional options (only prompt if not already set via flags)
|
|
242
|
+
this.log('');
|
|
243
|
+
const additionalPrompts = [];
|
|
244
|
+
if (flags.production === undefined || flags.production === false) {
|
|
245
|
+
additionalPrompts.push({
|
|
246
|
+
default: false,
|
|
247
|
+
message: chalk.yellow('🏭 Is this a production stage?'),
|
|
248
|
+
name: 'production',
|
|
249
|
+
type: 'confirm',
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
if (flags.deletionProtection === undefined || flags.deletionProtection === false) {
|
|
253
|
+
additionalPrompts.push({
|
|
254
|
+
default: false,
|
|
255
|
+
message: chalk.yellow('🔒 Enable deletion protection?'),
|
|
256
|
+
name: 'deletionProtection',
|
|
257
|
+
type: 'confirm',
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
if (flags.defaultStage === undefined || flags.defaultStage === false) {
|
|
261
|
+
additionalPrompts.push({
|
|
262
|
+
default: false,
|
|
263
|
+
message: chalk.yellow('⭐ Set as default stage (fallback)?'),
|
|
264
|
+
name: 'defaultStage',
|
|
265
|
+
type: 'confirm',
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
if (flags.autoLaunch === undefined || flags.autoLaunch === false) {
|
|
269
|
+
additionalPrompts.push({
|
|
270
|
+
default: false,
|
|
271
|
+
message: chalk.yellow('🚀 Auto-launch deployments after creation?'),
|
|
272
|
+
name: 'autoLaunch',
|
|
273
|
+
type: 'confirm',
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
if (additionalPrompts.length > 0) {
|
|
277
|
+
const additionalResponses = await inquirer.prompt(additionalPrompts);
|
|
278
|
+
Object.assign(responses, additionalResponses);
|
|
279
|
+
}
|
|
280
|
+
// Clean up internal properties
|
|
281
|
+
delete responses._defaultRegion;
|
|
282
|
+
return responses;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Stream mission logs in real-time
|
|
286
|
+
*/
|
|
287
|
+
async streamMissionLogs(loggingConfig, missionId) {
|
|
288
|
+
this.log(chalk.blue(`\n🚀 Mission started: ${missionId}`));
|
|
289
|
+
this.log(chalk.gray(` Launching all modules in parallel...`));
|
|
290
|
+
this.log('');
|
|
291
|
+
const tailer = new CloudWatchLogTailer(loggingConfig, {
|
|
292
|
+
pollInterval: 1000,
|
|
293
|
+
showDebug: false,
|
|
294
|
+
verbose: true,
|
|
295
|
+
});
|
|
296
|
+
const outcome = await tailer.tail();
|
|
297
|
+
this.log('');
|
|
298
|
+
if (outcome.success) {
|
|
299
|
+
this.log(chalk.green(`✅ ${outcome.message}`));
|
|
300
|
+
this.log(chalk.cyan(` Mission: ${missionId}`));
|
|
301
|
+
const consoleUrl = this.buildConsoleUrl(loggingConfig);
|
|
302
|
+
this.log(chalk.gray(` Logs: ${consoleUrl}`));
|
|
303
|
+
this.exit(0);
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
this.log(chalk.red(`❌ ${outcome.message}`));
|
|
307
|
+
const consoleUrl = this.buildConsoleUrl(loggingConfig);
|
|
308
|
+
this.log(chalk.gray(` Full logs: ${consoleUrl}`));
|
|
309
|
+
this.exit(1);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class StageList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
4
|
+
export default class StageList extends Command {
|
|
5
|
+
static description = 'List all stages';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
domain: Flags.string({
|
|
11
|
+
char: 'd',
|
|
12
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { flags } = await this.parse(StageList);
|
|
17
|
+
try {
|
|
18
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
19
|
+
this.log(chalk.blue('🔍 Fetching stages...'));
|
|
20
|
+
const stages = await service.stageList();
|
|
21
|
+
if (!stages || stages.length === 0) {
|
|
22
|
+
this.log(chalk.yellow('⚠️ No stages found'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.log(chalk.green(`✅ Found ${stages.length} stage(s)\n`));
|
|
26
|
+
// Display stages in a table
|
|
27
|
+
ux.table(stages, {
|
|
28
|
+
branchName: {
|
|
29
|
+
header: 'Branch',
|
|
30
|
+
},
|
|
31
|
+
createdAt: {
|
|
32
|
+
get: (row) => new Date(row.createdAt).toLocaleDateString(),
|
|
33
|
+
header: 'Created',
|
|
34
|
+
},
|
|
35
|
+
defaultStage: {
|
|
36
|
+
get: (row) => row.defaultStage ? chalk.green('✓') : '',
|
|
37
|
+
header: 'Default',
|
|
38
|
+
},
|
|
39
|
+
name: {
|
|
40
|
+
header: 'Name',
|
|
41
|
+
minWidth: 15,
|
|
42
|
+
},
|
|
43
|
+
production: {
|
|
44
|
+
get: (row) => row.production ? chalk.red('PROD') : chalk.blue('DEV'),
|
|
45
|
+
header: 'Type',
|
|
46
|
+
},
|
|
47
|
+
regions: {
|
|
48
|
+
get: (row) => (row.regions || []).join(', '),
|
|
49
|
+
header: 'Regions',
|
|
50
|
+
},
|
|
51
|
+
slug: {
|
|
52
|
+
get: (row) => chalk.cyan(row.slug),
|
|
53
|
+
header: 'Slug',
|
|
54
|
+
minWidth: 15,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
console.error('Error:', error);
|
|
60
|
+
this.error('An error occurred while listing stages');
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class TestApi extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { HyperdriveSigV4Service } from '../services/hyperdrive-sigv4.js';
|
|
4
|
+
export default class TestApi extends Command {
|
|
5
|
+
static description = 'Test API connection with SigV4 authentication';
|
|
6
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
7
|
+
static flags = {
|
|
8
|
+
domain: Flags.string({
|
|
9
|
+
char: 'd',
|
|
10
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
11
|
+
}),
|
|
12
|
+
};
|
|
13
|
+
async run() {
|
|
14
|
+
const { flags } = await this.parse(TestApi);
|
|
15
|
+
try {
|
|
16
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
17
|
+
this.log(chalk.blue('🧪 Testing Hyperdrive API Connection'));
|
|
18
|
+
this.log('');
|
|
19
|
+
this.log(chalk.gray('Attempting to call: GET /'));
|
|
20
|
+
this.log('');
|
|
21
|
+
// Try to call the root endpoint
|
|
22
|
+
const response = await service.makeTestRequest();
|
|
23
|
+
this.log(chalk.green('✅ API Connection Successful!'));
|
|
24
|
+
this.log('');
|
|
25
|
+
this.log(chalk.white('Response:'));
|
|
26
|
+
this.log(JSON.stringify(response, null, 2));
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
this.log(chalk.red('❌ API Connection Failed'));
|
|
30
|
+
this.log('');
|
|
31
|
+
if (error instanceof Error) {
|
|
32
|
+
this.log(chalk.yellow('Error:'), error.message);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
this.log(chalk.yellow('Error:'), String(error));
|
|
36
|
+
}
|
|
37
|
+
this.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export interface CognitoTokens {
|
|
2
|
+
access_token: string;
|
|
3
|
+
expires_in: number;
|
|
4
|
+
id_token: string;
|
|
5
|
+
refresh_token: string;
|
|
6
|
+
token_type: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AWSCredentials {
|
|
9
|
+
accessKeyId: string;
|
|
10
|
+
expiration: Date;
|
|
11
|
+
secretAccessKey: string;
|
|
12
|
+
sessionToken: string;
|
|
13
|
+
}
|
|
14
|
+
export interface CognitoConfig {
|
|
15
|
+
clientId: string;
|
|
16
|
+
domain: string;
|
|
17
|
+
identityPoolId: string;
|
|
18
|
+
userPoolId: string;
|
|
19
|
+
}
|
|
20
|
+
export interface StoredCredentials extends CognitoTokens {
|
|
21
|
+
apiUrl: string;
|
|
22
|
+
awsCredentials: AWSCredentials;
|
|
23
|
+
cognitoConfig?: CognitoConfig;
|
|
24
|
+
obtainedAt: string;
|
|
25
|
+
region: string;
|
|
26
|
+
tenantDomain: string;
|
|
27
|
+
tenantId: string;
|
|
28
|
+
}
|
|
29
|
+
export declare class AuthService {
|
|
30
|
+
private readonly cognitoConfig;
|
|
31
|
+
private readonly credDir;
|
|
32
|
+
private readonly credPath;
|
|
33
|
+
private readonly domain?;
|
|
34
|
+
constructor(domain?: string);
|
|
35
|
+
/**
|
|
36
|
+
* Clear stored credentials
|
|
37
|
+
*/
|
|
38
|
+
clearCredentials(): void;
|
|
39
|
+
/**
|
|
40
|
+
* Ensure credentials are valid, refresh if needed
|
|
41
|
+
* Returns valid credentials or throws error
|
|
42
|
+
*/
|
|
43
|
+
ensureValidCredentials(): Promise<StoredCredentials>;
|
|
44
|
+
/**
|
|
45
|
+
* Get AWS credentials from Cognito Identity Pool using ID token
|
|
46
|
+
*/
|
|
47
|
+
getAWSCredentials(idToken: string, region: string, cognitoConfig: CognitoConfig): Promise<AWSCredentials>;
|
|
48
|
+
/**
|
|
49
|
+
* Get all domains with stored credentials
|
|
50
|
+
*/
|
|
51
|
+
getCredentialDomains(): string[];
|
|
52
|
+
/**
|
|
53
|
+
* Check if credentials are expired
|
|
54
|
+
*/
|
|
55
|
+
isExpired(credentials: StoredCredentials): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Load stored credentials from domain-specific path
|
|
58
|
+
*
|
|
59
|
+
* Always uses domain-specific credentials (credentials.<domain>)
|
|
60
|
+
* Domain is either explicitly specified or uses default domain
|
|
61
|
+
*/
|
|
62
|
+
loadCredentials(): StoredCredentials | null;
|
|
63
|
+
/**
|
|
64
|
+
* Check if credentials need refresh
|
|
65
|
+
* Returns true if AWS credentials expire in less than 5 minutes
|
|
66
|
+
*/
|
|
67
|
+
needsRefresh(credentials: StoredCredentials): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Refresh Cognito tokens using refresh token
|
|
70
|
+
*/
|
|
71
|
+
refreshTokens(refreshToken: string, cognitoConfig: CognitoConfig): Promise<CognitoTokens>;
|
|
72
|
+
/**
|
|
73
|
+
* Save credentials to ~/.hyperdrive/credentials
|
|
74
|
+
*/
|
|
75
|
+
saveCredentials(credentials: StoredCredentials): void;
|
|
76
|
+
/**
|
|
77
|
+
* Get the credentials file path (always domain-specific)
|
|
78
|
+
*/
|
|
79
|
+
private getCredentialsPath;
|
|
80
|
+
/**
|
|
81
|
+
* Get default domain from TenantService (avoid circular dependency by reading file directly)
|
|
82
|
+
*/
|
|
83
|
+
private getDefaultDomain;
|
|
84
|
+
}
|