@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,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class JiraConnect 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,141 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import inquirer from 'inquirer';
4
+ import ora from 'ora';
5
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
6
+ export default class JiraConnect extends Command {
7
+ static description = 'Register your Jira instance with Hyperdrive (run BEFORE installing the Forge app)';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %>',
10
+ '<%= config.bin %> <%= command.id %> --domain dev-squad.atlassian.net',
11
+ ];
12
+ static flags = {
13
+ domain: Flags.string({
14
+ char: 'd',
15
+ description: 'Your Jira domain (e.g., dev-squad.atlassian.net)',
16
+ }),
17
+ };
18
+ async run() {
19
+ const { flags } = await this.parse(JiraConnect);
20
+ this.log('');
21
+ this.log(chalk.blue.bold('šŸ”Œ Hyperdrive Jira Integration Setup'));
22
+ this.log('');
23
+ this.log(chalk.dim('This wizard will pre-register your Jira instance with Hyperdrive.'));
24
+ this.log(chalk.dim('After registration, you\'ll install the Forge app from the Atlassian Marketplace.'));
25
+ this.log('');
26
+ // Create API service (automatically checks auth)
27
+ let apiService;
28
+ const spinner = ora('Checking authentication...').start();
29
+ try {
30
+ apiService = new HyperdriveSigV4Service();
31
+ spinner.succeed('Authenticated');
32
+ }
33
+ catch (error) {
34
+ spinner.fail('Not authenticated');
35
+ this.error(`${error.message}\n\n` +
36
+ `Please authenticate first with: ${chalk.cyan('hd auth login')}`);
37
+ }
38
+ // Get Jira domain
39
+ let jiraDomain = flags.domain;
40
+ if (!jiraDomain) {
41
+ this.log('');
42
+ const answers = await inquirer.prompt([
43
+ {
44
+ default: 'your-company.atlassian.net',
45
+ filter: (input) => {
46
+ // Normalize domain
47
+ return input
48
+ .replace(/^https?:\/\//, '')
49
+ .replace(/\/$/, '')
50
+ .toLowerCase();
51
+ },
52
+ message: 'Enter your Jira domain:',
53
+ name: 'jiraDomain',
54
+ type: 'input',
55
+ validate: (input) => {
56
+ // Normalize and validate
57
+ const normalized = input
58
+ .replace(/^https?:\/\//, '')
59
+ .replace(/\/$/, '')
60
+ .toLowerCase();
61
+ if (!normalized.includes('.atlassian.net')) {
62
+ return 'Invalid Jira domain. Expected format: your-site.atlassian.net';
63
+ }
64
+ return true;
65
+ }
66
+ }
67
+ ]);
68
+ jiraDomain = answers.jiraDomain;
69
+ }
70
+ else {
71
+ // Normalize provided domain
72
+ jiraDomain = jiraDomain
73
+ .replace(/^https?:\/\//, '')
74
+ .replace(/\/$/, '')
75
+ .toLowerCase();
76
+ }
77
+ // Pre-register domain
78
+ spinner.start('Registering Jira domain with Hyperdrive...');
79
+ try {
80
+ const response = await apiService['makeSignedRequest']('POST', '/hyperdrive/jira/pre-register', {
81
+ jiraDomain,
82
+ });
83
+ spinner.succeed('Jira domain registered successfully');
84
+ // Display registration details
85
+ this.log('');
86
+ this.log(chalk.green('āœ… Pre-registration Complete!'));
87
+ this.log('');
88
+ this.log(chalk.bold('Registration Details:'));
89
+ this.log(` Jira Domain: ${chalk.cyan(response.registration.jiraDomain)}`);
90
+ this.log(` Tenant ID: ${chalk.dim(response.registration.tenantId)}`);
91
+ this.log(` Registration Token: ${chalk.cyan(response.registration.token)}`);
92
+ this.log(` Status: ${chalk.yellow(response.registration.status)}`);
93
+ this.log('');
94
+ this.log(chalk.bold('Next Steps:'));
95
+ response.nextSteps.instructions.forEach((instruction, index) => {
96
+ this.log(` ${chalk.cyan(`${index + 1}.`)} ${instruction}`);
97
+ });
98
+ this.log('');
99
+ this.log(chalk.bold('Marketplace URL:'));
100
+ this.log(` ${chalk.cyan(response.nextSteps.marketplaceUrl)}`);
101
+ this.log('');
102
+ this.log(chalk.dim('šŸ’” Tip: After installing the Forge app, run:'));
103
+ this.log(chalk.dim(` ${chalk.cyan('hd jira status')} - to verify the connection`));
104
+ this.log('');
105
+ this.log(chalk.yellow('āš ļø Important: You must install the Forge app for the integration to work!'));
106
+ this.log('');
107
+ }
108
+ catch (error) {
109
+ spinner.fail('Failed to register Jira domain');
110
+ // Parse error message
111
+ let errorMessage = error.message;
112
+ if (error.response) {
113
+ const status = error.response.status;
114
+ const data = error.response.data;
115
+ if (status === 401) {
116
+ errorMessage = 'Authentication failed - please run "hd auth login"';
117
+ }
118
+ else if (status === 403) {
119
+ errorMessage = 'Access denied - check your permissions';
120
+ }
121
+ else if (status === 400 && data?.error) {
122
+ errorMessage = data.error;
123
+ }
124
+ else if (status === 404) {
125
+ errorMessage = 'Pre-register endpoint not found - is the API deployed?';
126
+ }
127
+ else if (data && data.error) {
128
+ errorMessage = data.error;
129
+ }
130
+ else if (data && data.message) {
131
+ errorMessage = data.message;
132
+ }
133
+ }
134
+ this.error(`${errorMessage}\n\n` +
135
+ `Troubleshooting:\n` +
136
+ ` ${chalk.cyan('•')} Ensure you're authenticated: ${chalk.cyan('hd auth login')}\n` +
137
+ ` ${chalk.cyan('•')} Verify the API is deployed and accessible\n` +
138
+ ` ${chalk.cyan('•')} Check that the pre-register endpoint exists: /hyperdrive/jira/pre-register`);
139
+ }
140
+ }
141
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class JiraStatus 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,118 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ export default class JiraStatus extends Command {
6
+ static description = 'Check the status of your Jira integration';
7
+ static examples = [
8
+ '<%= config.bin %> <%= command.id %>',
9
+ ];
10
+ static flags = {
11
+ domain: Flags.string({
12
+ char: 'd',
13
+ description: 'Tenant domain (for multi-domain setups)',
14
+ }),
15
+ };
16
+ async run() {
17
+ const { flags } = await this.parse(JiraStatus);
18
+ this.log('');
19
+ this.log(chalk.blue.bold('šŸ”Œ Hyperdrive Jira Integration Status'));
20
+ this.log('');
21
+ // Create API service (automatically checks auth)
22
+ let apiService;
23
+ const spinner = ora('Checking authentication...').start();
24
+ try {
25
+ apiService = new HyperdriveSigV4Service(flags.domain);
26
+ spinner.succeed('Authenticated');
27
+ }
28
+ catch (error) {
29
+ spinner.fail('Not authenticated');
30
+ this.error(`${error.message}\n\n` +
31
+ `Please authenticate first with: ${chalk.cyan('hd auth login')}`);
32
+ }
33
+ // Fetch status
34
+ spinner.start('Fetching Jira connection status...');
35
+ try {
36
+ const response = await apiService['makeSignedRequest']('GET', '/hyperdrive/jira/status');
37
+ spinner.succeed('Status retrieved');
38
+ // Display summary
39
+ this.log('');
40
+ this.log(chalk.bold('Summary:'));
41
+ this.log(` Tenant ID: ${chalk.dim(response.tenantId)}`);
42
+ this.log(` Active Connections: ${chalk.cyan(response.summary.activeConnections)}`);
43
+ this.log(` Total Connections: ${chalk.cyan(response.summary.totalConnections)}`);
44
+ this.log(` Pending Registrations: ${chalk.yellow(response.summary.pendingRegistrations)}`);
45
+ this.log('');
46
+ // Display active connections
47
+ if (response.connections.length > 0) {
48
+ this.log(chalk.bold('Active Jira Connections:'));
49
+ response.connections.forEach((conn, index) => {
50
+ const statusColor = conn.status === 'active' ? chalk.green : chalk.red;
51
+ this.log(` ${chalk.cyan(`${index + 1}.`)} ${chalk.cyan(conn.jiraDomain)}`);
52
+ this.log(` Cloud ID: ${chalk.dim(conn.cloudId)}`);
53
+ this.log(` Status: ${statusColor(conn.status)}`);
54
+ this.log(` Installed: ${chalk.dim(new Date(conn.installedAt).toLocaleString())}`);
55
+ });
56
+ this.log('');
57
+ }
58
+ // Display pending registrations
59
+ if (response.pendingRegistrations.length > 0) {
60
+ this.log(chalk.bold('Pending Registrations:'));
61
+ this.log(chalk.dim('These Jira instances are registered but the Forge app has not been installed yet.'));
62
+ this.log('');
63
+ response.pendingRegistrations.forEach((reg, index) => {
64
+ this.log(` ${chalk.yellow(`${index + 1}.`)} ${chalk.cyan(reg.jiraDomain)}`);
65
+ this.log(` Token: ${chalk.cyan(reg.token)}`);
66
+ this.log(` Status: ${chalk.yellow(reg.status)}`);
67
+ this.log(` Registered: ${chalk.dim(new Date(reg.registeredAt).toLocaleString())}`);
68
+ });
69
+ this.log('');
70
+ this.log(chalk.yellow('āš ļø Action Required: Install the Forge app from the Atlassian Marketplace'));
71
+ this.log(chalk.dim(` Run: ${chalk.cyan('hd jira connect')} to see installation instructions`));
72
+ this.log('');
73
+ }
74
+ // No connections or registrations
75
+ if (response.connections.length === 0 && response.pendingRegistrations.length === 0) {
76
+ this.log(chalk.yellow('No Jira connections found'));
77
+ this.log('');
78
+ this.log(chalk.dim('To connect a Jira instance, run:'));
79
+ this.log(chalk.dim(` ${chalk.cyan('hd jira connect')}`));
80
+ this.log('');
81
+ }
82
+ // Success message if everything is connected
83
+ if (response.summary.activeConnections > 0 && response.summary.pendingRegistrations === 0) {
84
+ this.log(chalk.green('āœ… All Jira instances are connected and ready!'));
85
+ this.log('');
86
+ }
87
+ }
88
+ catch (error) {
89
+ spinner.fail('Failed to fetch status');
90
+ // Parse error message
91
+ let errorMessage = error.message;
92
+ if (error.response) {
93
+ const status = error.response.status;
94
+ const data = error.response.data;
95
+ if (status === 401) {
96
+ errorMessage = 'Authentication failed - please run "hd auth login"';
97
+ }
98
+ else if (status === 403) {
99
+ errorMessage = 'Access denied - check your permissions';
100
+ }
101
+ else if (status === 404) {
102
+ errorMessage = 'Status endpoint not found - is the API deployed?';
103
+ }
104
+ else if (data && data.error) {
105
+ errorMessage = data.error;
106
+ }
107
+ else if (data && data.message) {
108
+ errorMessage = data.message;
109
+ }
110
+ }
111
+ this.error(`${errorMessage}\n\n` +
112
+ `Troubleshooting:\n` +
113
+ ` ${chalk.cyan('•')} Ensure you're authenticated: ${chalk.cyan('hd auth login')}\n` +
114
+ ` ${chalk.cyan('•')} Verify the API is deployed and accessible\n` +
115
+ ` ${chalk.cyan('•')} Check that the status endpoint exists: /hyperdrive/jira/status`);
116
+ }
117
+ }
118
+ }
@@ -0,0 +1,29 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ModuleAnalyze 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
+ save: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
8
+ slug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ verbose: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
10
+ wait: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
11
+ };
12
+ run(): Promise<void>;
13
+ /**
14
+ * Build CloudWatch console URL for log viewing
15
+ */
16
+ private buildConsoleUrl;
17
+ /**
18
+ * Display analysis results
19
+ */
20
+ private displayResults;
21
+ /**
22
+ * Poll for Dockerfile (fallback when streaming not available)
23
+ */
24
+ private pollForDockerfile;
25
+ /**
26
+ * Stream analysis logs using CloudWatch
27
+ */
28
+ private streamAnalysisLogs;
29
+ }
@@ -0,0 +1,201 @@
1
+ import { Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ import { CloudWatchLogTailer } from '../../services/log-tailer.js';
6
+ export default class ModuleAnalyze extends Command {
7
+ static description = 'Analyze a module and generate an optimized Dockerfile using AI';
8
+ static examples = [
9
+ '<%= config.bin %> <%= command.id %> --slug="my-module"',
10
+ '<%= config.bin %> <%= command.id %>',
11
+ '<%= config.bin %> <%= command.id %> --slug="my-module" --verbose',
12
+ ];
13
+ static flags = {
14
+ domain: Flags.string({
15
+ char: 'd',
16
+ description: 'Tenant domain (for multi-domain setups)',
17
+ }),
18
+ save: Flags.boolean({
19
+ default: false,
20
+ description: 'Save generated Dockerfile to current directory',
21
+ }),
22
+ slug: Flags.string({
23
+ char: 's',
24
+ default() {
25
+ if (existsSync('.hyperdrive.json')) {
26
+ const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
27
+ return hyperdriveConfig.slug;
28
+ }
29
+ return '';
30
+ },
31
+ description: 'Module slug to analyze',
32
+ required: true,
33
+ }),
34
+ verbose: Flags.boolean({
35
+ char: 'v',
36
+ default: false,
37
+ description: 'Show detailed analysis progress and real-time logs',
38
+ }),
39
+ wait: Flags.boolean({
40
+ char: 'w',
41
+ default: true,
42
+ description: 'Wait for analysis to complete and show results',
43
+ }),
44
+ };
45
+ async run() {
46
+ const { flags } = await this.parse(ModuleAnalyze);
47
+ try {
48
+ const service = new HyperdriveSigV4Service(flags.domain);
49
+ this.log(chalk.blue(`šŸ” Analyzing module ${chalk.cyan(flags.slug)}...`));
50
+ this.log(chalk.gray('This will use AI to generate an optimized Dockerfile for your project.\n'));
51
+ if (flags.verbose) {
52
+ this.log(chalk.gray('šŸ“ Verbose mode enabled - streaming real-time logs\n'));
53
+ }
54
+ const result = await service.moduleAnalyze(flags.slug);
55
+ this.log(chalk.green(`āœ… Analysis queued successfully!`));
56
+ this.log(chalk.gray(` Job ID: ${result.jobId}`));
57
+ this.log(chalk.gray(` Status: ${result.status}`));
58
+ if (flags.verbose && result.logging) {
59
+ this.log(chalk.gray(` Project ID: ${result.projectId}`));
60
+ }
61
+ if (!flags.wait) {
62
+ this.log(chalk.yellow('\nUse --wait to poll for results, or check later with:'));
63
+ this.log(chalk.cyan(` hyperdrive module dockerfile --slug ${flags.slug}`));
64
+ return;
65
+ }
66
+ // If verbose and API returned logging config, stream logs
67
+ if (flags.verbose && result.logging) {
68
+ await this.streamAnalysisLogs(result.logging, flags, service);
69
+ }
70
+ else {
71
+ // Fallback to polling
72
+ if (flags.verbose && !result.logging) {
73
+ this.log(chalk.yellow('āš ļø Log streaming not available (API limitation), falling back to polling'));
74
+ }
75
+ this.log(chalk.blue('\nā³ Waiting for analysis to complete...'));
76
+ this.log(chalk.gray(' This typically takes 2-5 minutes.\n'));
77
+ const dockerfile = await this.pollForDockerfile(service, flags.slug);
78
+ if (!dockerfile) {
79
+ this.log(chalk.yellow('\nāš ļø Analysis is still in progress.'));
80
+ this.log(chalk.gray(' Check back later with:'));
81
+ this.log(chalk.cyan(` hyperdrive module dockerfile --slug ${flags.slug}`));
82
+ return;
83
+ }
84
+ await this.displayResults(dockerfile, flags);
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.error('Error:', error);
89
+ this.error(`Failed to analyze module ${flags.slug}`);
90
+ }
91
+ }
92
+ /**
93
+ * Build CloudWatch console URL for log viewing
94
+ */
95
+ buildConsoleUrl(config) {
96
+ const encodedGroup = encodeURIComponent(encodeURIComponent(config.logGroup));
97
+ const encodedStream = encodeURIComponent(encodeURIComponent(config.logStream));
98
+ return `https://${config.region}.console.aws.amazon.com/cloudwatch/home?region=${config.region}#logsV2:log-groups/log-group/${encodedGroup}/log-events/${encodedStream}`;
99
+ }
100
+ /**
101
+ * Display analysis results
102
+ */
103
+ async displayResults(dockerfile, flags) {
104
+ if (!dockerfile) {
105
+ this.log(chalk.yellow('\nāš ļø No Dockerfile found'));
106
+ return;
107
+ }
108
+ // Show results
109
+ this.log(chalk.green('\nāœ… Dockerfile generated successfully!\n'));
110
+ if (dockerfile.analysis) {
111
+ this.log(chalk.blue('šŸ“Š Analysis Results:'));
112
+ this.log(chalk.gray(` Runtime: ${dockerfile.analysis.detectedRuntime || 'unknown'}`));
113
+ this.log(chalk.gray(` Framework: ${dockerfile.analysis.detectedFramework || 'unknown'}`));
114
+ if (dockerfile.analysis.port) {
115
+ this.log(chalk.gray(` Port: ${dockerfile.analysis.port}`));
116
+ }
117
+ if (dockerfile.analysis.healthEndpoint) {
118
+ this.log(chalk.gray(` Health: ${dockerfile.analysis.healthEndpoint}`));
119
+ }
120
+ }
121
+ this.log(chalk.blue('\n🐳 Validation Results:'));
122
+ this.log(chalk.gray(` Build: ${dockerfile.buildSuccess ? chalk.green('āœ“ Success') : chalk.red('āœ— Failed')}`));
123
+ this.log(chalk.gray(` Run: ${dockerfile.runSuccess ? chalk.green('āœ“ Success') : chalk.red('āœ— Failed')}`));
124
+ this.log(chalk.gray(` Port: ${dockerfile.portOpen ? chalk.green('āœ“ Open') : chalk.red('āœ— Closed')}`));
125
+ if (dockerfile.analysis?.recommendations?.length) {
126
+ this.log(chalk.blue('\nšŸ’” Recommendations:'));
127
+ for (const rec of dockerfile.analysis.recommendations) {
128
+ this.log(chalk.gray(` • ${rec}`));
129
+ }
130
+ }
131
+ this.log(chalk.blue('\nšŸ“„ Generated Dockerfile:'));
132
+ this.log(chalk.gray('─'.repeat(60)));
133
+ this.log(dockerfile.dockerfile);
134
+ this.log(chalk.gray('─'.repeat(60)));
135
+ if (flags.save) {
136
+ writeFileSync('Dockerfile', dockerfile.dockerfile);
137
+ this.log(chalk.green('\nāœ… Dockerfile saved to ./Dockerfile'));
138
+ if (dockerfile.dockerignore) {
139
+ writeFileSync('.dockerignore', dockerfile.dockerignore);
140
+ this.log(chalk.green('āœ… .dockerignore saved to ./.dockerignore'));
141
+ }
142
+ }
143
+ else {
144
+ this.log(chalk.gray('\nTip: Use --save to write files to current directory'));
145
+ }
146
+ }
147
+ /**
148
+ * Poll for Dockerfile (fallback when streaming not available)
149
+ */
150
+ async pollForDockerfile(service, slug, maxAttempts = 60, intervalMs = 5000) {
151
+ for (let i = 0; i < maxAttempts; i++) {
152
+ try {
153
+ const result = await service.moduleGetDockerfile(slug);
154
+ if (result) {
155
+ return result;
156
+ }
157
+ }
158
+ catch {
159
+ // Not ready yet
160
+ }
161
+ // Show progress
162
+ process.stdout.write(chalk.gray('.'));
163
+ await new Promise(resolve => setTimeout(resolve, intervalMs));
164
+ }
165
+ return null;
166
+ }
167
+ /**
168
+ * Stream analysis logs using CloudWatch
169
+ */
170
+ async streamAnalysisLogs(loggingConfig, flags, service) {
171
+ const tailer = new CloudWatchLogTailer(loggingConfig, {
172
+ pollInterval: 1000,
173
+ showDebug: false,
174
+ verbose: true,
175
+ });
176
+ this.log('');
177
+ const outcome = await tailer.tail();
178
+ this.log('');
179
+ if (outcome.success) {
180
+ this.log(chalk.green(`āœ… ${outcome.message}`));
181
+ // Show CloudWatch console link
182
+ const consoleUrl = this.buildConsoleUrl(loggingConfig);
183
+ this.log(chalk.gray(` Logs: ${consoleUrl}`));
184
+ // Fetch generated Dockerfile
185
+ const dockerfile = await service.moduleGetDockerfile(flags.slug);
186
+ if (dockerfile) {
187
+ await this.displayResults(dockerfile, flags);
188
+ }
189
+ else {
190
+ this.log(chalk.yellow('\nāš ļø Dockerfile not found yet, may still be processing'));
191
+ }
192
+ }
193
+ else {
194
+ this.log(chalk.red(`āŒ ${outcome.message}`));
195
+ // Show CloudWatch console link for debugging
196
+ const consoleUrl = this.buildConsoleUrl(loggingConfig);
197
+ this.log(chalk.gray(` Full logs: ${consoleUrl}`));
198
+ this.exit(1);
199
+ }
200
+ }
201
+ }
@@ -0,0 +1,42 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ModuleCreate extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ buildCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
7
+ buildDirectory: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
8
+ buildFolder: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
9
+ buildRuntime: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
10
+ buildRuntimeVersion: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
11
+ ciService: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
12
+ defaultBranch: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
13
+ domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
14
+ framework: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
15
+ installCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
16
+ name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
17
+ runCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
18
+ runtime: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
19
+ runtimeVersion: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
20
+ slug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
21
+ sourceDirectory: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
22
+ sourceLocation: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
23
+ };
24
+ run(): Promise<void>;
25
+ /**
26
+ * Convert any git URL (SSH or HTTPS) to HTTPS format for the API
27
+ */
28
+ private convertToHttpsUrl;
29
+ private getDefaultBranch;
30
+ private getGitOrigin;
31
+ /**
32
+ * Offer AI-powered Dockerfile generation after project creation
33
+ */
34
+ private offerDockerfileGeneration;
35
+ private parseGitUrl;
36
+ /**
37
+ * Poll for Dockerfile generation status
38
+ */
39
+ private pollDockerfileStatus;
40
+ private promptUser;
41
+ private suggestSlug;
42
+ }