@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,188 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import moment from 'moment';
4
+ import { existsSync, readFileSync } from 'node:fs';
5
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
6
+ import { CloudWatchLogTailer } from '../../services/log-tailer.js';
7
+ export default class DeploymentCreate extends Command {
8
+ static description = 'Create a new deployment with real-time log streaming';
9
+ static examples = [
10
+ '<%= config.bin %> <%= command.id %> --stage="dev" --commit="abc123" --moduleSlug="my-api"',
11
+ '<%= config.bin %> <%= command.id %> --stage="dev" --commit="abc123" --launch',
12
+ '<%= config.bin %> <%= command.id %> --stage="dev" --commit="abc123" --verbose',
13
+ ];
14
+ static flags = {
15
+ commit: Flags.string({
16
+ char: 'c',
17
+ description: 'Commit hash',
18
+ required: true,
19
+ }),
20
+ debug: Flags.boolean({
21
+ default: false,
22
+ description: 'Include debug-level logs (requires --verbose)',
23
+ }),
24
+ domain: Flags.string({
25
+ char: 'd',
26
+ description: 'Tenant domain (for multi-domain setups)',
27
+ }),
28
+ launch: Flags.boolean({
29
+ default: false,
30
+ description: 'Launch the deployment after creating it',
31
+ }),
32
+ moduleSlug: Flags.string({
33
+ char: 'm',
34
+ default() {
35
+ if (existsSync('.hyperdrive.json')) {
36
+ const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
37
+ return hyperdriveConfig.slug;
38
+ }
39
+ return '';
40
+ },
41
+ description: 'Module slug',
42
+ required: true,
43
+ }),
44
+ name: Flags.string({
45
+ char: 'n',
46
+ default: `Deployment-${moment().format('YYYY-MM-DD-HH-mm-ss')}`,
47
+ description: 'The name of the deployment',
48
+ required: false,
49
+ }),
50
+ noStream: Flags.boolean({
51
+ default: false,
52
+ description: 'Disable log streaming (use polling instead)',
53
+ }),
54
+ regions: Flags.string({
55
+ char: 'r',
56
+ default: [],
57
+ description: 'Regions for the deployment',
58
+ multiple: true,
59
+ }),
60
+ stage: Flags.string({
61
+ char: 's',
62
+ description: 'Stage for the deployment',
63
+ required: true,
64
+ }),
65
+ verbose: Flags.boolean({
66
+ char: 'v',
67
+ default: false,
68
+ description: 'Show detailed build logs',
69
+ }),
70
+ };
71
+ async run() {
72
+ const { flags } = await this.parse(DeploymentCreate);
73
+ this.log(chalk.blue(`\n📦 Creating deployment "${flags.name}"...`));
74
+ this.log(chalk.gray(` Module: ${flags.moduleSlug}`));
75
+ this.log(chalk.gray(` Stage: ${flags.stage}`));
76
+ this.log(chalk.gray(` Commit: ${flags.commit}`));
77
+ if (flags.regions.length > 0) {
78
+ this.log(chalk.gray(` Regions: ${flags.regions.join(', ')}`));
79
+ }
80
+ this.log('');
81
+ try {
82
+ const service = new HyperdriveSigV4Service(flags.domain);
83
+ const result = await service.deploymentCreate({
84
+ commit: flags.commit,
85
+ launch: flags.launch,
86
+ name: flags.name,
87
+ projectSlug: flags.moduleSlug, // API still expects projectSlug (backend terminology)
88
+ regions: flags.regions,
89
+ stage: flags.stage,
90
+ });
91
+ // Check if API returned logging config (for real-time streaming)
92
+ const loggingConfig = result.logging;
93
+ if (flags.noStream || !loggingConfig) {
94
+ // Fallback to polling (existing behavior)
95
+ if (!flags.noStream && !loggingConfig) {
96
+ this.log(chalk.gray('Log streaming not available, using polling...'));
97
+ }
98
+ let statusToExpected = 'ready';
99
+ if (flags.launch) {
100
+ statusToExpected = 'launched';
101
+ }
102
+ await this.pollDeploymentStatus(service, flags, statusToExpected);
103
+ }
104
+ else {
105
+ // Stream logs using scoped credentials
106
+ await this.streamDeploymentLogs(loggingConfig, flags, result);
107
+ }
108
+ }
109
+ catch (error) {
110
+ const err = error;
111
+ // Ignore EEXIT with code 0 - this is oclif's way of exiting cleanly
112
+ if (err.message?.includes('EEXIT') && err.oclif?.exit === 0) {
113
+ return;
114
+ }
115
+ if (err.response?.status === 400 && err.response?.data === 'Deployment is not in ready state, so cannot be "recreated"') {
116
+ this.error(err.response.data);
117
+ }
118
+ this.log(chalk.red('❌ Error creating deployment: ' + err.message));
119
+ this.exit(1);
120
+ }
121
+ }
122
+ /**
123
+ * Build CloudWatch console URL for log viewing
124
+ */
125
+ buildConsoleUrl(config) {
126
+ const encodedGroup = encodeURIComponent(encodeURIComponent(config.logGroup));
127
+ const encodedStream = encodeURIComponent(encodeURIComponent(config.logStream));
128
+ return `https://${config.region}.console.aws.amazon.com/cloudwatch/home?region=${config.region}#logsV2:log-groups/log-group/${encodedGroup}/log-events/${encodedStream}`;
129
+ }
130
+ /**
131
+ * Poll for deployment status (fallback when streaming not available)
132
+ */
133
+ async pollDeploymentStatus(service, flags, expectedStatus) {
134
+ const response = await service.deploymentCheck({
135
+ name: flags.name,
136
+ projectSlug: flags.moduleSlug, // API still expects projectSlug
137
+ stage: flags.stage,
138
+ });
139
+ this.log(chalk.blue(`📋 Deployment status: ${response.status}`));
140
+ if (response.status === expectedStatus) {
141
+ this.log(chalk.green(`✅ Deployment ${expectedStatus}!`));
142
+ if (response.url) {
143
+ this.log(chalk.cyan(`🌐 URL: ${response.url}`));
144
+ }
145
+ return;
146
+ }
147
+ if (response.status === 'failed') {
148
+ this.log(chalk.red('❌ Deployment failed'));
149
+ if (response.error) {
150
+ this.log(chalk.red(` Error: ${response.error}`));
151
+ }
152
+ this.exit(1);
153
+ }
154
+ await new Promise(resolve => {
155
+ setTimeout(resolve, 10_000);
156
+ });
157
+ return this.pollDeploymentStatus(service, flags, expectedStatus);
158
+ }
159
+ /**
160
+ * Stream deployment logs using CloudWatch
161
+ */
162
+ async streamDeploymentLogs(loggingConfig, flags, result) {
163
+ const tailer = new CloudWatchLogTailer(loggingConfig, {
164
+ pollInterval: 1000,
165
+ showDebug: flags.debug,
166
+ verbose: flags.verbose,
167
+ });
168
+ const outcome = await tailer.tail();
169
+ this.log('');
170
+ if (outcome.success) {
171
+ this.log(chalk.green(`✅ ${outcome.message}`));
172
+ if (result.url) {
173
+ this.log(chalk.cyan(` URL: ${result.url}`));
174
+ }
175
+ // Show CloudWatch console link
176
+ const consoleUrl = this.buildConsoleUrl(loggingConfig);
177
+ this.log(chalk.gray(` Logs: ${consoleUrl}`));
178
+ this.exit(0);
179
+ }
180
+ else {
181
+ this.log(chalk.red(`❌ ${outcome.message}`));
182
+ // Show CloudWatch console link for debugging
183
+ const consoleUrl = this.buildConsoleUrl(loggingConfig);
184
+ this.log(chalk.gray(` Full logs: ${consoleUrl}`));
185
+ this.exit(1);
186
+ }
187
+ }
188
+ }
@@ -0,0 +1,13 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DeploymentGet 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
+ moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
+ };
11
+ run(): Promise<void>;
12
+ private colorizeStatus;
13
+ }
@@ -0,0 +1,101 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ export default class DeploymentGet extends Command {
6
+ static description = 'Get details of a specific deployment (alias for deployment check)';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %> --name="Deployment-2024-01-01" --stage="dev"',
9
+ '<%= config.bin %> <%= command.id %> --name="prod-release-v1" --stage="prod"',
10
+ ];
11
+ static flags = {
12
+ domain: Flags.string({
13
+ char: 'd',
14
+ description: 'Tenant domain (for multi-domain setups)',
15
+ }),
16
+ moduleSlug: Flags.string({
17
+ char: 'm',
18
+ default() {
19
+ if (existsSync('.hyperdrive.json')) {
20
+ const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
21
+ return hyperdriveConfig.slug;
22
+ }
23
+ return '';
24
+ },
25
+ description: 'Module slug',
26
+ required: true,
27
+ }),
28
+ name: Flags.string({
29
+ char: 'n',
30
+ description: 'Deployment name',
31
+ required: true,
32
+ }),
33
+ stage: Flags.string({
34
+ char: 's',
35
+ description: 'Stage for the deployment',
36
+ required: true,
37
+ }),
38
+ };
39
+ async run() {
40
+ const { flags } = await this.parse(DeploymentGet);
41
+ try {
42
+ const service = new HyperdriveSigV4Service(flags.domain);
43
+ this.log(chalk.blue(`🔍 Fetching deployment ${chalk.cyan(flags.name)}...`));
44
+ const deployment = await service.deploymentCheck({
45
+ name: flags.name,
46
+ projectSlug: flags.moduleSlug, // API still expects projectSlug
47
+ stage: flags.stage,
48
+ });
49
+ if (!deployment) {
50
+ this.log(chalk.red(`❌ Deployment ${flags.name} not found`));
51
+ return;
52
+ }
53
+ this.log(chalk.green('✅ Deployment details:\n'));
54
+ // Display key information
55
+ this.log(chalk.bold('Name:'), deployment.name || flags.name);
56
+ this.log(chalk.bold('Status:'), this.colorizeStatus(deployment.status));
57
+ this.log(chalk.bold('Stage:'), chalk.cyan(flags.stage));
58
+ this.log(chalk.bold('Project:'), chalk.cyan(flags.moduleSlug));
59
+ if (deployment.commit) {
60
+ this.log(chalk.bold('Commit:'), deployment.commit);
61
+ }
62
+ if (deployment.url) {
63
+ this.log(chalk.bold('URL:'), chalk.blue(deployment.url));
64
+ }
65
+ if (deployment.createdAt && typeof deployment.createdAt === 'string') {
66
+ this.log(chalk.bold('Created:'), new Date(deployment.createdAt).toLocaleString());
67
+ }
68
+ if (deployment.regions) {
69
+ this.log(chalk.bold('Regions:'), deployment.regions);
70
+ }
71
+ if (deployment.error) {
72
+ this.log(chalk.bold(chalk.red('Error:')), deployment.error);
73
+ }
74
+ // Show full JSON for additional details
75
+ this.log(chalk.gray('\n📋 Full details:'));
76
+ this.log(JSON.stringify(deployment, null, 2));
77
+ }
78
+ catch (error) {
79
+ console.error('Error:', error);
80
+ this.error(`An error occurred while fetching deployment ${flags.name}`);
81
+ }
82
+ }
83
+ colorizeStatus(status) {
84
+ if (!status)
85
+ return chalk.gray('UNKNOWN');
86
+ switch (status.toLowerCase()) {
87
+ case 'completed':
88
+ case 'launched':
89
+ case 'ready':
90
+ return chalk.green(status.toUpperCase());
91
+ case 'failed':
92
+ return chalk.red(status.toUpperCase());
93
+ case 'pending':
94
+ case 'building':
95
+ case 'deploying':
96
+ return chalk.yellow(status.toUpperCase());
97
+ default:
98
+ return chalk.gray(status.toUpperCase());
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,15 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DeploymentLaunch 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
+ force: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
8
+ moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
+ region: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
+ stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ private pollDeploymentStatus;
15
+ }
@@ -0,0 +1,105 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ export default class DeploymentLaunch extends Command {
6
+ static description = 'Launch an existing deployment';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %> --name="Deployment-2024-01-01" --stage="dev" --region="us-east-1"',
9
+ '<%= config.bin %> <%= command.id %> --name="prod-release" --stage="prod" --region="us-west-2"',
10
+ ];
11
+ static flags = {
12
+ domain: Flags.string({
13
+ char: 'd',
14
+ description: 'Tenant domain (for multi-domain setups)',
15
+ }),
16
+ force: Flags.boolean({
17
+ char: 'f',
18
+ default: false,
19
+ description: 'Force launch even if deployment is not in ready state',
20
+ }),
21
+ moduleSlug: Flags.string({
22
+ char: 'm',
23
+ default() {
24
+ if (existsSync('.hyperdrive.json')) {
25
+ const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
26
+ return hyperdriveConfig.slug;
27
+ }
28
+ return '';
29
+ },
30
+ description: 'Module slug',
31
+ required: true,
32
+ }),
33
+ name: Flags.string({
34
+ char: 'n',
35
+ description: 'The name of the deployment to launch',
36
+ required: true,
37
+ }),
38
+ region: Flags.string({
39
+ char: 'r',
40
+ description: 'Region for the deployment',
41
+ }),
42
+ stage: Flags.string({
43
+ char: 's',
44
+ description: 'Stage for the deployment',
45
+ required: true,
46
+ }),
47
+ };
48
+ async run() {
49
+ const { flags } = await this.parse(DeploymentLaunch);
50
+ this.log(chalk.green(`🚀 Launching deployment "${flags.name}"...`));
51
+ this.log(chalk.gray(` Project: ${flags.moduleSlug}`));
52
+ this.log(chalk.gray(` Stage: ${flags.stage}`));
53
+ if (flags.region) {
54
+ this.log(chalk.gray(` Region: ${flags.region}`));
55
+ }
56
+ if (flags.force) {
57
+ this.log(chalk.yellow(` Force: true`));
58
+ }
59
+ try {
60
+ const service = new HyperdriveSigV4Service(flags.domain);
61
+ await service.deploymentLaunch({
62
+ force: flags.force,
63
+ name: flags.name,
64
+ projectSlug: flags.moduleSlug, // API still expects projectSlug
65
+ region: flags.region,
66
+ stage: flags.stage,
67
+ });
68
+ await this.pollDeploymentStatus(service, flags);
69
+ }
70
+ catch (error) {
71
+ const err = error;
72
+ if (err.response?.status === 400 && err.response?.data === 'Deployment is not in ready state, so cannot be "launched"') {
73
+ this.error(err.response.data);
74
+ }
75
+ this.log(chalk.red('❌ Error launching deployment: ' + err.message));
76
+ this.exit(1);
77
+ }
78
+ }
79
+ async pollDeploymentStatus(service, flags) {
80
+ const response = await service.deploymentCheck({
81
+ name: flags.name,
82
+ projectSlug: flags.moduleSlug, // API still expects projectSlug
83
+ stage: flags.stage,
84
+ });
85
+ this.log(chalk.blue(`📋 Deployment status: ${response.status}`));
86
+ if (response.status === 'completed' || response.status === 'launched') {
87
+ this.log(chalk.green('✅ Deployment launched successfully!'));
88
+ if (response.url) {
89
+ this.log(chalk.cyan(`🌐 URL: ${response.url}`));
90
+ }
91
+ return;
92
+ }
93
+ if (response.status === 'failed') {
94
+ this.log(chalk.red('❌ Deployment failed'));
95
+ if (response.error) {
96
+ this.log(chalk.red(` Error: ${response.error}`));
97
+ }
98
+ this.exit(1);
99
+ }
100
+ await new Promise(resolve => {
101
+ setTimeout(resolve, 10_000);
102
+ });
103
+ return this.pollDeploymentStatus(service, flags);
104
+ }
105
+ }
@@ -0,0 +1,11 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DeploymentList 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
+ moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,91 @@
1
+ import { Command, Flags, ux } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ export default class DeploymentList extends Command {
6
+ static description = 'List all deployments for a module and stage';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %> --stage="dev"',
9
+ '<%= config.bin %> <%= command.id %> --stage="prod" --moduleSlug="my-api"',
10
+ ];
11
+ static flags = {
12
+ domain: Flags.string({
13
+ char: 'd',
14
+ description: 'Tenant domain (for multi-domain setups)',
15
+ }),
16
+ moduleSlug: Flags.string({
17
+ char: 'm',
18
+ default() {
19
+ if (existsSync('.hyperdrive.json')) {
20
+ const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
21
+ return hyperdriveConfig.slug;
22
+ }
23
+ return '';
24
+ },
25
+ description: 'Module slug',
26
+ required: true,
27
+ }),
28
+ stage: Flags.string({
29
+ char: 's',
30
+ description: 'Stage to list deployments for',
31
+ required: true,
32
+ }),
33
+ };
34
+ async run() {
35
+ const { flags } = await this.parse(DeploymentList);
36
+ try {
37
+ const service = new HyperdriveSigV4Service(flags.domain);
38
+ this.log(chalk.blue(`🔍 Fetching deployments for ${chalk.cyan(flags.moduleSlug)} in stage ${chalk.cyan(flags.stage)}...`));
39
+ const deployments = await service.deploymentList({
40
+ projectSlug: flags.moduleSlug, // API still expects projectSlug
41
+ stage: flags.stage,
42
+ });
43
+ if (!deployments || deployments.length === 0) {
44
+ this.log(chalk.yellow('⚠️ No deployments found'));
45
+ return;
46
+ }
47
+ this.log(chalk.green(`✅ Found ${deployments.length} deployment(s)\n`));
48
+ // Display deployments in a table
49
+ ux.table(deployments, {
50
+ commit: {
51
+ get: (row) => row.commit?.substring(0, 7) || 'N/A',
52
+ header: 'Commit',
53
+ },
54
+ createdAt: {
55
+ get: (row) => new Date(row.createdAt).toLocaleString(),
56
+ header: 'Created',
57
+ },
58
+ name: {
59
+ header: 'Name',
60
+ minWidth: 20,
61
+ },
62
+ regions: {
63
+ get: (row) => (row.regions || []).join(', '),
64
+ header: 'Regions',
65
+ },
66
+ status: {
67
+ get: (row) => {
68
+ const status = row.status || 'unknown';
69
+ switch (status) {
70
+ case 'completed':
71
+ case 'launched':
72
+ return chalk.green(status.toUpperCase());
73
+ case 'failed':
74
+ return chalk.red(status.toUpperCase());
75
+ case 'pending':
76
+ case 'building':
77
+ return chalk.yellow(status.toUpperCase());
78
+ default:
79
+ return chalk.gray(status.toUpperCase());
80
+ }
81
+ },
82
+ header: 'Status',
83
+ },
84
+ });
85
+ }
86
+ catch (error) {
87
+ console.error('Error:', error);
88
+ this.error('An error occurred while listing deployments');
89
+ }
90
+ }
91
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DomainCurrent extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,18 @@
1
+ import { Command } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { TenantService } from '../../services/tenant-service.js';
4
+ export default class DomainCurrent extends Command {
5
+ static description = 'Show the current default domain';
6
+ static examples = ['<%= config.bin %> <%= command.id %>'];
7
+ async run() {
8
+ const tenantService = new TenantService();
9
+ const defaultDomain = tenantService.getDefaultDomain();
10
+ if (defaultDomain) {
11
+ this.log(chalk.green(`Current default domain: ${chalk.cyan(defaultDomain)}`));
12
+ }
13
+ else {
14
+ this.log(chalk.yellow('No default domain set.'));
15
+ this.log(chalk.gray('Run `hd auth login` to set up a domain.'));
16
+ }
17
+ }
18
+ }
@@ -0,0 +1,6 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DomainList extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { AuthService } from '../../services/auth-service.js';
4
+ import { TenantService } from '../../services/tenant-service.js';
5
+ export default class DomainList extends Command {
6
+ static description = 'List all configured domains/installations';
7
+ static examples = ['<%= config.bin %> <%= command.id %>'];
8
+ async run() {
9
+ const tenantService = new TenantService();
10
+ const authService = new AuthService();
11
+ // Get configured domains and authenticated domains
12
+ const configuredDomains = tenantService.getConfiguredDomains();
13
+ const credentialDomains = authService.getCredentialDomains();
14
+ const defaultDomain = tenantService.getDefaultDomain();
15
+ // Merge both lists
16
+ const allDomains = new Set([...configuredDomains, ...credentialDomains]);
17
+ if (allDomains.size === 0) {
18
+ this.log(chalk.yellow('No domains configured yet.'));
19
+ this.log(chalk.gray('Run `hd auth login --domain <domain>` to add a domain.'));
20
+ return;
21
+ }
22
+ this.log(chalk.blue('📋 Configured Domains:'));
23
+ this.log('');
24
+ for (const domain of Array.from(allDomains).sort()) {
25
+ const isDefault = domain === defaultDomain;
26
+ const hasConfig = configuredDomains.includes(domain);
27
+ const hasCredentials = credentialDomains.includes(domain);
28
+ let status = '';
29
+ if (hasCredentials) {
30
+ status = chalk.green('✓ Authenticated');
31
+ }
32
+ else if (hasConfig) {
33
+ status = chalk.yellow('⚠ Not authenticated');
34
+ }
35
+ const defaultBadge = isDefault ? chalk.cyan(' [DEFAULT]') : '';
36
+ this.log(` ${chalk.white(domain)}${defaultBadge} ${status}`);
37
+ }
38
+ this.log('');
39
+ this.log(chalk.gray('💡 Use `hd domain switch <domain>` to change the default domain'));
40
+ this.log(chalk.gray('💡 Use `--domain <domain>` flag to run commands for a specific domain'));
41
+ }
42
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class DomainSwitch extends Command {
3
+ static args: {
4
+ domain: import("@oclif/core/lib/interfaces/parser.js").Arg<string, Record<string, unknown>>;
5
+ };
6
+ static description: string;
7
+ static examples: string[];
8
+ run(): Promise<void>;
9
+ }
@@ -0,0 +1,40 @@
1
+ import { Args, Command } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { AuthService } from '../../services/auth-service.js';
4
+ import { TenantService } from '../../services/tenant-service.js';
5
+ export default class DomainSwitch extends Command {
6
+ static args = {
7
+ domain: Args.string({
8
+ description: 'The domain to set as default',
9
+ required: true,
10
+ }),
11
+ };
12
+ static description = 'Switch the default domain for CLI commands';
13
+ static examples = [
14
+ '<%= config.bin %> <%= command.id %> acme.hyperdrive.bot',
15
+ ];
16
+ async run() {
17
+ const { args } = await this.parse(DomainSwitch);
18
+ const tenantService = new TenantService();
19
+ const authService = new AuthService();
20
+ // Verify domain exists
21
+ const credentialDomains = authService.getCredentialDomains();
22
+ const configuredDomains = tenantService.getConfiguredDomains();
23
+ const allDomains = [...credentialDomains, ...configuredDomains];
24
+ if (!allDomains.includes(args.domain)) {
25
+ this.error(chalk.red(`Domain "${args.domain}" not found.\n\n`) +
26
+ chalk.yellow('Available domains:\n') +
27
+ (allDomains.length > 0
28
+ ? allDomains.map(d => ` - ${d}`).join('\n')
29
+ : chalk.gray(' No domains configured yet.')) +
30
+ '\n\n' +
31
+ chalk.gray('💡 Run `hd auth login --domain <domain>` to add a new domain.'));
32
+ }
33
+ // Set as default
34
+ tenantService.setDefaultDomain(args.domain);
35
+ this.log(chalk.green(`✅ Default domain switched to: ${chalk.cyan(args.domain)}`));
36
+ this.log('');
37
+ this.log(chalk.gray('All commands will now use this domain by default.'));
38
+ this.log(chalk.gray('You can still use `--domain <domain>` to override temporarily.'));
39
+ }
40
+ }