@hyperdrive.bot/cli 1.0.12 → 1.0.16

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 (157) hide show
  1. package/README.md +1495 -474
  2. package/dist/commands/deploy.d.ts +18 -0
  3. package/dist/commands/deploy.js +239 -0
  4. package/dist/commands/deployment/create.js +10 -2
  5. package/dist/commands/domain/{switch.d.ts → set-production.d.ts} +1 -1
  6. package/dist/commands/domain/set-production.js +27 -0
  7. package/dist/commands/git/list-open-prs.d.ts +12 -0
  8. package/dist/commands/git/list-open-prs.js +87 -0
  9. package/dist/commands/hook/add.d.ts +22 -0
  10. package/dist/commands/hook/add.js +299 -0
  11. package/dist/commands/hook/list.d.ts +11 -0
  12. package/dist/commands/hook/list.js +111 -0
  13. package/dist/commands/hook/logs.d.ts +13 -0
  14. package/dist/commands/hook/logs.js +124 -0
  15. package/dist/commands/hook/remove.d.ts +12 -0
  16. package/dist/commands/hook/remove.js +115 -0
  17. package/dist/commands/hook/toggle.d.ts +12 -0
  18. package/dist/commands/hook/toggle.js +125 -0
  19. package/dist/commands/init.d.ts +1 -1
  20. package/dist/commands/init.js +49 -9
  21. package/dist/commands/module/bindings.d.ts +14 -0
  22. package/dist/commands/module/bindings.js +125 -0
  23. package/dist/commands/module/create.d.ts +3 -0
  24. package/dist/commands/module/create.js +156 -78
  25. package/dist/commands/module/list.d.ts +1 -0
  26. package/dist/commands/module/list.js +22 -1
  27. package/dist/commands/module/sync.d.ts +29 -0
  28. package/dist/commands/module/sync.js +409 -0
  29. package/dist/commands/module/unlink.d.ts +11 -0
  30. package/dist/commands/module/unlink.js +77 -0
  31. package/dist/commands/module/update.d.ts +10 -0
  32. package/dist/commands/module/update.js +168 -5
  33. package/dist/commands/network/discover.d.ts +12 -0
  34. package/dist/commands/network/discover.js +210 -0
  35. package/dist/commands/network/get.d.ts +13 -0
  36. package/dist/commands/network/get.js +90 -0
  37. package/dist/commands/{auth/logout.d.ts → network/list.d.ts} +2 -9
  38. package/dist/commands/network/list.js +71 -0
  39. package/dist/commands/network/register.d.ts +16 -0
  40. package/dist/commands/network/register.js +144 -0
  41. package/dist/commands/parameter/sync.d.ts +13 -0
  42. package/dist/commands/parameter/sync.js +69 -1
  43. package/dist/commands/project/sync.d.ts +5 -11
  44. package/dist/commands/project/sync.js +12 -381
  45. package/dist/commands/seed.d.ts +93 -0
  46. package/dist/commands/seed.js +324 -0
  47. package/dist/commands/service/backup.d.ts +17 -0
  48. package/dist/commands/service/backup.js +156 -0
  49. package/dist/commands/service/backups.d.ts +14 -0
  50. package/dist/commands/service/backups.js +110 -0
  51. package/dist/commands/service/bind.d.ts +16 -0
  52. package/dist/commands/service/bind.js +106 -0
  53. package/dist/commands/service/bindings.d.ts +13 -0
  54. package/dist/commands/service/bindings.js +78 -0
  55. package/dist/commands/service/clone.d.ts +19 -0
  56. package/dist/commands/service/clone.js +153 -0
  57. package/dist/commands/service/create.d.ts +16 -0
  58. package/dist/commands/service/create.js +212 -0
  59. package/dist/commands/service/get.d.ts +13 -0
  60. package/dist/commands/service/get.js +97 -0
  61. package/dist/commands/service/list.d.ts +12 -0
  62. package/dist/commands/service/list.js +86 -0
  63. package/dist/commands/service/register.d.ts +21 -0
  64. package/dist/commands/service/register.js +215 -0
  65. package/dist/commands/service/restore.d.ts +19 -0
  66. package/dist/commands/service/restore.js +158 -0
  67. package/dist/commands/service/seed.d.ts +17 -0
  68. package/dist/commands/service/seed.js +173 -0
  69. package/dist/commands/service/templates.d.ts +10 -0
  70. package/dist/commands/service/templates.js +66 -0
  71. package/dist/commands/service/unbind.d.ts +15 -0
  72. package/dist/commands/service/unbind.js +74 -0
  73. package/dist/commands/stage/create.d.ts +23 -0
  74. package/dist/commands/stage/create.js +145 -6
  75. package/dist/commands/stage/delete.d.ts +11 -0
  76. package/dist/commands/stage/delete.js +85 -0
  77. package/dist/commands/stage/deploy.d.ts +34 -0
  78. package/dist/commands/stage/deploy.js +294 -0
  79. package/dist/commands/stage/ensure-branches.d.ts +23 -0
  80. package/dist/commands/stage/ensure-branches.js +101 -0
  81. package/dist/commands/stage/list.js +4 -0
  82. package/dist/commands/stage/status.d.ts +14 -0
  83. package/dist/commands/stage/status.js +100 -0
  84. package/dist/commands/{jira → tracker}/connect.js +32 -23
  85. package/dist/commands/tracker/hook/add.d.ts +25 -0
  86. package/dist/commands/tracker/hook/add.js +284 -0
  87. package/dist/commands/{jira → tracker}/hook/list.js +20 -11
  88. package/dist/commands/{jira/hook/add.d.ts → tracker/hook/logs.d.ts} +2 -3
  89. package/dist/commands/tracker/hook/logs.js +126 -0
  90. package/dist/commands/{jira → tracker}/hook/remove.js +9 -8
  91. package/dist/commands/{jira → tracker}/hook/toggle.js +14 -12
  92. package/dist/commands/tracker/project/init.d.ts +17 -0
  93. package/dist/commands/tracker/project/init.js +178 -0
  94. package/dist/commands/tracker/project/link-module.d.ts +17 -0
  95. package/dist/commands/tracker/project/link-module.js +287 -0
  96. package/dist/commands/tracker/project/list-modules.d.ts +11 -0
  97. package/dist/commands/tracker/project/list-modules.js +117 -0
  98. package/dist/commands/tracker/project/list.d.ts +10 -0
  99. package/dist/commands/tracker/project/list.js +90 -0
  100. package/dist/commands/tracker/project/status.d.ts +13 -0
  101. package/dist/commands/tracker/project/status.js +168 -0
  102. package/dist/commands/tracker/project/unlink-module.d.ts +13 -0
  103. package/dist/commands/tracker/project/unlink-module.js +251 -0
  104. package/dist/commands/{jira → tracker}/status.js +3 -3
  105. package/dist/lib/ensure-branches.d.ts +53 -0
  106. package/dist/lib/ensure-branches.js +149 -0
  107. package/dist/lib/git-providers/github.d.ts +16 -0
  108. package/dist/lib/git-providers/github.js +157 -0
  109. package/dist/lib/git-providers/gitlab.d.ts +16 -0
  110. package/dist/lib/git-providers/gitlab.js +148 -0
  111. package/dist/lib/git-providers/index.d.ts +67 -0
  112. package/dist/lib/git-providers/index.js +39 -0
  113. package/dist/lib/lambda-warmer.d.ts +106 -0
  114. package/dist/lib/lambda-warmer.js +189 -0
  115. package/dist/services/hyperdrive-sigv4.d.ts +360 -5
  116. package/dist/services/hyperdrive-sigv4.js +192 -24
  117. package/dist/utils/hook-flow.d.ts +60 -3
  118. package/dist/utils/hook-flow.js +437 -2
  119. package/dist/utils/hook-normalize.d.ts +6 -0
  120. package/dist/utils/hook-normalize.js +33 -0
  121. package/dist/utils/lifecycle-poller.d.ts +32 -0
  122. package/dist/utils/lifecycle-poller.js +72 -0
  123. package/dist/utils/retry.d.ts +43 -0
  124. package/dist/utils/retry.js +88 -0
  125. package/dist/utils/summary-display.js +1 -1
  126. package/dist/utils/tracker-project-flow.d.ts +84 -0
  127. package/dist/utils/tracker-project-flow.js +564 -0
  128. package/package.json +35 -7
  129. package/dist/commands/auth/login.d.ts +0 -16
  130. package/dist/commands/auth/login.js +0 -179
  131. package/dist/commands/auth/logout.js +0 -116
  132. package/dist/commands/auth/refresh.d.ts +0 -6
  133. package/dist/commands/auth/refresh.js +0 -66
  134. package/dist/commands/auth/status.d.ts +0 -6
  135. package/dist/commands/auth/status.js +0 -63
  136. package/dist/commands/config/get.d.ts +0 -9
  137. package/dist/commands/config/get.js +0 -37
  138. package/dist/commands/config/set.d.ts +0 -10
  139. package/dist/commands/config/set.js +0 -48
  140. package/dist/commands/config/show.d.ts +0 -6
  141. package/dist/commands/config/show.js +0 -10
  142. package/dist/commands/domain/current.d.ts +0 -6
  143. package/dist/commands/domain/current.js +0 -18
  144. package/dist/commands/domain/list.d.ts +0 -6
  145. package/dist/commands/domain/list.js +0 -42
  146. package/dist/commands/domain/switch.js +0 -40
  147. package/dist/commands/jira/hook/add.js +0 -147
  148. package/dist/services/tenant-service.d.ts +0 -127
  149. package/dist/services/tenant-service.js +0 -396
  150. package/dist/utils/auth-flow.d.ts +0 -147
  151. package/dist/utils/auth-flow.js +0 -479
  152. package/oclif.manifest.json +0 -3519
  153. /package/dist/commands/{jira → tracker}/connect.d.ts +0 -0
  154. /package/dist/commands/{jira → tracker}/hook/list.d.ts +0 -0
  155. /package/dist/commands/{jira → tracker}/hook/remove.d.ts +0 -0
  156. /package/dist/commands/{jira → tracker}/hook/toggle.d.ts +0 -0
  157. /package/dist/commands/{jira → tracker}/status.d.ts +0 -0
@@ -0,0 +1,115 @@
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
+ import { promptConfirmDelete, promptSelectHook } from '../../utils/hook-flow.js';
6
+ export default class HookRemove extends Command {
7
+ static description = 'Delete a tenant lifecycle hook';
8
+ static examples = [
9
+ '<%= config.bin %> hook remove',
10
+ '<%= config.bin %> hook remove --hook-id 01JQXYZ',
11
+ '<%= config.bin %> hook remove --hook-id 01JQXYZ --json',
12
+ ];
13
+ static flags = {
14
+ domain: Flags.string({
15
+ char: 'd',
16
+ description: 'Hyperdrive tenant domain',
17
+ }),
18
+ 'hook-id': Flags.string({
19
+ description: 'Hook ID to remove (skips interactive selection)',
20
+ }),
21
+ json: Flags.boolean({
22
+ description: 'Output raw JSON',
23
+ }),
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(HookRemove);
27
+ const isJson = flags.json;
28
+ const hookIdFlag = flags['hook-id'];
29
+ // Authenticate
30
+ let apiService;
31
+ const spinner = isJson ? null : ora('Checking authentication...').start();
32
+ try {
33
+ apiService = new HyperdriveSigV4Service(flags.domain);
34
+ spinner?.succeed('Authenticated');
35
+ }
36
+ catch (error) {
37
+ spinner?.fail('Not authenticated');
38
+ this.error(`${error.message}\n\n` +
39
+ `Please authenticate first with: ${chalk.cyan('hd auth login')}`);
40
+ }
41
+ if (isJson && !hookIdFlag) {
42
+ this.error('Cannot use --json without --hook-id (interactive mode requires a terminal)');
43
+ }
44
+ let hookId;
45
+ if (hookIdFlag) {
46
+ hookId = hookIdFlag;
47
+ }
48
+ else {
49
+ // Fetch hooks for interactive selection
50
+ const fetchSpinner = ora('Fetching hooks...').start();
51
+ try {
52
+ const hooks = await apiService.tenantHookList();
53
+ fetchSpinner.succeed(`Found ${hooks.length} hook${hooks.length === 1 ? '' : 's'}`);
54
+ if (hooks.length === 0) {
55
+ this.log('');
56
+ this.log(chalk.yellow('No hooks to remove'));
57
+ this.log('');
58
+ return;
59
+ }
60
+ this.log('');
61
+ const selectedHook = await promptSelectHook(hooks);
62
+ hookId = selectedHook.hookId;
63
+ const confirmed = await promptConfirmDelete(selectedHook);
64
+ if (!confirmed) {
65
+ this.log(chalk.dim('Cancelled'));
66
+ return;
67
+ }
68
+ }
69
+ catch (error) {
70
+ fetchSpinner.fail('Failed to fetch hooks');
71
+ this.handleApiError(error);
72
+ }
73
+ }
74
+ // Delete hook
75
+ const deleteSpinner = isJson ? null : ora('Deleting hook...').start();
76
+ try {
77
+ await apiService.tenantHookDelete(hookId);
78
+ deleteSpinner?.succeed('Hook deleted');
79
+ if (isJson) {
80
+ this.log(JSON.stringify({ message: 'Hook deleted', hookId: hookId }, null, 2));
81
+ return;
82
+ }
83
+ this.log('');
84
+ this.log(chalk.green('Hook deleted: ') + chalk.cyan(hookId));
85
+ this.log('');
86
+ }
87
+ catch (error) {
88
+ deleteSpinner?.fail('Failed to delete hook');
89
+ this.handleApiError(error);
90
+ }
91
+ }
92
+ handleApiError(error) {
93
+ let errorMessage = error.message;
94
+ if (error.response) {
95
+ const status = error.response.status;
96
+ const data = error.response.data;
97
+ if (status === 401) {
98
+ errorMessage = 'Authentication failed — please run "hd auth login"';
99
+ }
100
+ else if (status === 403) {
101
+ errorMessage = 'Access denied — check your permissions';
102
+ }
103
+ else if (status === 404) {
104
+ errorMessage = 'Hook not found';
105
+ }
106
+ else if (data?.error) {
107
+ errorMessage = data.error;
108
+ }
109
+ else if (data?.message) {
110
+ errorMessage = data.message;
111
+ }
112
+ }
113
+ this.error(errorMessage);
114
+ }
115
+ }
@@ -0,0 +1,12 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class HookToggle extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static flags: {
6
+ domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
7
+ 'hook-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ private handleApiError;
12
+ }
@@ -0,0 +1,125 @@
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
+ import { promptSelectHook } from '../../utils/hook-flow.js';
6
+ export default class HookToggle extends Command {
7
+ static description = 'Enable or disable a tenant lifecycle hook';
8
+ static examples = [
9
+ '<%= config.bin %> hook toggle',
10
+ '<%= config.bin %> hook toggle --hook-id 01JQXYZ',
11
+ '<%= config.bin %> hook toggle --hook-id 01JQXYZ --json',
12
+ ];
13
+ static flags = {
14
+ domain: Flags.string({
15
+ char: 'd',
16
+ description: 'Hyperdrive tenant domain',
17
+ }),
18
+ 'hook-id': Flags.string({
19
+ description: 'Hook ID to toggle (skips interactive selection)',
20
+ }),
21
+ json: Flags.boolean({
22
+ description: 'Output raw JSON',
23
+ }),
24
+ };
25
+ async run() {
26
+ const { flags } = await this.parse(HookToggle);
27
+ const isJson = flags.json;
28
+ const hookIdFlag = flags['hook-id'];
29
+ // Authenticate
30
+ let apiService;
31
+ const spinner = isJson ? null : ora('Checking authentication...').start();
32
+ try {
33
+ apiService = new HyperdriveSigV4Service(flags.domain);
34
+ spinner?.succeed('Authenticated');
35
+ }
36
+ catch (error) {
37
+ spinner?.fail('Not authenticated');
38
+ this.error(`${error.message}\n\n` +
39
+ `Please authenticate first with: ${chalk.cyan('hd auth login')}`);
40
+ }
41
+ if (isJson && !hookIdFlag) {
42
+ this.error('Cannot use --json without --hook-id (interactive mode requires a terminal)');
43
+ }
44
+ let hookId;
45
+ let currentEnabled;
46
+ if (hookIdFlag) {
47
+ hookId = hookIdFlag;
48
+ }
49
+ else {
50
+ // Fetch hooks for interactive selection
51
+ const fetchSpinner = ora('Fetching hooks...').start();
52
+ try {
53
+ const hooks = await apiService.tenantHookList();
54
+ fetchSpinner.succeed(`Found ${hooks.length} hook${hooks.length === 1 ? '' : 's'}`);
55
+ if (hooks.length === 0) {
56
+ this.log('');
57
+ this.log(chalk.yellow('No hooks to toggle'));
58
+ this.log('');
59
+ return;
60
+ }
61
+ this.log('');
62
+ const selectedHook = await promptSelectHook(hooks);
63
+ hookId = selectedHook.hookId;
64
+ currentEnabled = selectedHook.enabled;
65
+ }
66
+ catch (error) {
67
+ fetchSpinner.fail('Failed to fetch hooks');
68
+ this.handleApiError(error);
69
+ }
70
+ }
71
+ // Toggle hook
72
+ const toggleSpinner = isJson ? null : ora('Toggling hook...').start();
73
+ try {
74
+ // If we don't know the current state (--hook-id path), fetch it first
75
+ let enabled;
76
+ if (currentEnabled !== undefined) {
77
+ enabled = !currentEnabled;
78
+ }
79
+ else {
80
+ const hook = await apiService.tenantHookGet(hookId);
81
+ enabled = !hook.enabled;
82
+ }
83
+ const updatedHook = await apiService.tenantHookUpdate(hookId, { enabled });
84
+ toggleSpinner?.succeed('Hook toggled');
85
+ if (isJson) {
86
+ this.log(JSON.stringify(updatedHook, null, 2));
87
+ return;
88
+ }
89
+ this.log('');
90
+ this.log(`Hook ${chalk.cyan(hookId)} is now ${updatedHook.enabled ? chalk.green('enabled') : chalk.red('disabled')}`);
91
+ this.log(` Hook ID: ${chalk.cyan(updatedHook.hookId)}`);
92
+ this.log(` Trigger Event: ${chalk.cyan(updatedHook.trigger?.event ?? '')}`);
93
+ this.log(` Action Type: ${chalk.cyan(updatedHook.action?.type ?? '')}`);
94
+ this.log(` Enabled: ${updatedHook.enabled ? chalk.green('enabled') : chalk.red('disabled')}`);
95
+ this.log('');
96
+ }
97
+ catch (error) {
98
+ toggleSpinner?.fail('Failed to toggle hook');
99
+ this.handleApiError(error);
100
+ }
101
+ }
102
+ handleApiError(error) {
103
+ let errorMessage = error.message;
104
+ if (error.response) {
105
+ const status = error.response.status;
106
+ const data = error.response.data;
107
+ if (status === 401) {
108
+ errorMessage = 'Authentication failed — please run "hd auth login"';
109
+ }
110
+ else if (status === 403) {
111
+ errorMessage = 'Access denied — check your permissions';
112
+ }
113
+ else if (status === 404) {
114
+ errorMessage = 'Hook not found';
115
+ }
116
+ else if (data?.error) {
117
+ errorMessage = data.error;
118
+ }
119
+ else if (data?.message) {
120
+ errorMessage = data.message;
121
+ }
122
+ }
123
+ this.error(errorMessage);
124
+ }
125
+ }
@@ -1,6 +1,6 @@
1
1
  import { Command, Config } from '@oclif/core';
2
+ import type { AuthResult } from '@hyperdrive.bot/cli-auth';
2
3
  import { AccountResult } from '../utils/account-flow.js';
3
- import { AuthResult } from '../utils/auth-flow.js';
4
4
  import { GitConnectResult } from '../utils/git-flow.js';
5
5
  import { JiraConnectResult } from '../utils/jira-flow.js';
6
6
  export type AuthFlowFunction = (options: {
@@ -1,14 +1,54 @@
1
1
  import { Command } from '@oclif/core';
2
+ import { TenantService, buildAuthUrl, buildStoredCredentials, exchangeCodeForTokens, generateCodeChallenge, generateCodeVerifier, getAWSCredentialsFromIdentityPool, saveCredentials, startCallbackServer, } from '@hyperdrive.bot/cli-auth';
2
3
  import chalk from 'chalk';
3
4
  import inquirer from 'inquirer';
5
+ import open from 'open';
4
6
  import ora from 'ora';
5
- import { TenantService } from '../services/tenant-service.js';
6
7
  import { executeAccountAdd as executeAccountAddOriginal, openCloudFormationUrl, promptAccountDetails, registerAccount, waitForRoleVerification, } from '../utils/account-flow.js';
7
- import { executeAuthFlow as executeAuthFlowOriginal } from '../utils/auth-flow.js';
8
8
  import { executeGitConnect as executeGitConnectOriginal, promptGitProvider, } from '../utils/git-flow.js';
9
9
  import { executeJiraConnect as executeJiraConnectOriginal, promptJiraConnect, promptJiraDomain, registerJiraDomain, } from '../utils/jira-flow.js';
10
10
  import { displaySetupSummary } from '../utils/summary-display.js';
11
11
  import { validateTenantDomain } from '../utils/validation.js';
12
+ const HYPERDRIVE_TENANT_CONFIG = {
13
+ appName: 'hyperdrive',
14
+ defaultBootstrapUrl: 'https://api.hyperdrive.bot/tenant/bootstrap',
15
+ bootstrapUrlEnvVar: 'HYPERDRIVE_BOOTSTRAP_URL',
16
+ tenantDomainEnvVar: 'HYPERDRIVE_TENANT_DOMAIN',
17
+ apiUrlEnvVar: 'HYPERDRIVE_API_URL',
18
+ primaryApiName: 'hyperdrive',
19
+ };
20
+ /**
21
+ * Execute the complete OAuth PKCE authentication flow using cli-auth primitives.
22
+ */
23
+ async function executeAuthFlowOriginal(options) {
24
+ const { logger, tenantDomain } = options;
25
+ const port = 8765;
26
+ const tenantService = new TenantService(HYPERDRIVE_TENANT_CONFIG, tenantDomain);
27
+ try {
28
+ const tenantConfig = await tenantService.fetchTenantConfig(tenantDomain);
29
+ const codeVerifier = generateCodeVerifier();
30
+ const codeChallenge = generateCodeChallenge(codeVerifier);
31
+ const authCodePromise = startCallbackServer(port, 300_000, 'hyperdrive');
32
+ const authUrl = buildAuthUrl(tenantConfig, codeChallenge, port);
33
+ if (logger) {
34
+ logger('Opening browser for authentication...');
35
+ logger("If browser doesn't open or opens in wrong profile, copy this URL:");
36
+ logger(` ${authUrl}`);
37
+ logger('');
38
+ }
39
+ await open(authUrl);
40
+ const code = await authCodePromise;
41
+ const tokens = await exchangeCodeForTokens(tenantConfig, code, codeVerifier, port);
42
+ const awsCredentials = await getAWSCredentialsFromIdentityPool(tenantConfig, tokens.id_token);
43
+ const stored = buildStoredCredentials(tokens, awsCredentials, tenantConfig);
44
+ saveCredentials(stored, tenantConfig.tenantDomain, 'hyperdrive');
45
+ return { success: true };
46
+ }
47
+ catch (error) {
48
+ const errorMessage = error instanceof Error ? error.message : String(error);
49
+ return { error: errorMessage, success: false };
50
+ }
51
+ }
12
52
  // Module-level auth flow function - can be replaced for testing
13
53
  let authFlowImpl = executeAuthFlowOriginal;
14
54
  // Module-level account add flow function - can be replaced for testing
@@ -104,7 +144,7 @@ export default class Init extends Command {
104
144
  tenantService;
105
145
  constructor(argv, config) {
106
146
  super(argv, config);
107
- this.tenantService = new TenantService();
147
+ this.tenantService = new TenantService(HYPERDRIVE_TENANT_CONFIG);
108
148
  }
109
149
  async run() {
110
150
  this.displayWelcome();
@@ -492,7 +532,7 @@ export default class Init extends Command {
492
532
  }]);
493
533
  if (!tryAnyway) {
494
534
  this.jiraSkipped = true;
495
- this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
535
+ this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd tracker connect' later.");
496
536
  return;
497
537
  }
498
538
  }
@@ -500,7 +540,7 @@ export default class Init extends Command {
500
540
  const action = await promptJiraConnect(true);
501
541
  if (action === 'skip') {
502
542
  this.jiraSkipped = true;
503
- this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
543
+ this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd tracker connect' later.");
504
544
  return;
505
545
  }
506
546
  // Execute Jira connection flow with retry limit
@@ -642,7 +682,7 @@ export default class Init extends Command {
642
682
  this.log(chalk.bold('Marketplace URL:'));
643
683
  this.log(` ${chalk.cyan(result.marketplaceUrl || 'https://marketplace.atlassian.com')}`);
644
684
  this.log('');
645
- this.log(chalk.dim('💡 Tip: Run') + chalk.cyan(' hd jira status ') + chalk.dim('to verify the connection'));
685
+ this.log(chalk.dim('💡 Tip: Run') + chalk.cyan(' hd tracker status ') + chalk.dim('to verify the connection'));
646
686
  this.log('');
647
687
  }
648
688
  catch (error) {
@@ -721,12 +761,12 @@ export default class Init extends Command {
721
761
  }]);
722
762
  if (skip) {
723
763
  this.jiraSkipped = true;
724
- this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
764
+ this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd tracker connect' later.");
725
765
  }
726
766
  else {
727
767
  this.log('');
728
768
  this.log(chalk.yellow('⚠') + ' Setup cannot continue without Jira connection.');
729
- this.log(chalk.gray("Run 'hd jira connect' to connect Jira separately."));
769
+ this.log(chalk.gray("Run 'hd tracker connect' to connect Jira separately."));
730
770
  }
731
771
  return;
732
772
  }
@@ -744,7 +784,7 @@ export default class Init extends Command {
744
784
  else {
745
785
  // User chose not to retry - mark as skipped
746
786
  this.jiraSkipped = true;
747
- this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd jira connect' later.");
787
+ this.log(chalk.yellow('⚠') + " Skipped Jira integration. Run 'hd tracker connect' later.");
748
788
  }
749
789
  }
750
790
  /**
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class ModuleBindings extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ module: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
+ stage: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ };
13
+ run(): Promise<void>;
14
+ }
@@ -0,0 +1,125 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
5
+ import { printHeader, printTable } from '../../utils/table.js';
6
+ export default class ModuleBindings extends Command {
7
+ static description = 'List all service bindings for a module';
8
+ static examples = [
9
+ '<%= config.bin %> module bindings my-api',
10
+ '<%= config.bin %> module bindings my-api --stage live',
11
+ '<%= config.bin %> module bindings my-api --json',
12
+ ];
13
+ static args = {
14
+ module: Args.string({
15
+ description: 'Module slug',
16
+ required: true,
17
+ }),
18
+ };
19
+ static flags = {
20
+ domain: Flags.string({
21
+ char: 'd',
22
+ description: 'Tenant domain (for multi-domain setups)',
23
+ }),
24
+ json: Flags.boolean({
25
+ description: 'Output raw JSON response',
26
+ default: false,
27
+ }),
28
+ stage: Flags.string({
29
+ description: 'Filter by stage',
30
+ }),
31
+ };
32
+ async run() {
33
+ const { args, flags } = await this.parse(ModuleBindings);
34
+ const isJson = flags.json;
35
+ const service = new HyperdriveSigV4Service(flags.domain);
36
+ const spinner = isJson ? null : ora(`Fetching bindings for module "${args.module}"...`).start();
37
+ try {
38
+ const bindings = await service.moduleBindings(args.module, {
39
+ stage: flags.stage,
40
+ });
41
+ spinner?.stop();
42
+ if (isJson) {
43
+ this.log(JSON.stringify(bindings, null, 2));
44
+ return;
45
+ }
46
+ if (!bindings || bindings.length === 0) {
47
+ this.log(chalk.yellow(`\nNo service bindings found for module "${args.module}".`));
48
+ return;
49
+ }
50
+ this.log(chalk.green(`\n${bindings.length} binding(s) for module "${args.module}":\n`));
51
+ // Group by stage when no --stage filter
52
+ if (!flags.stage) {
53
+ const byStage = new Map();
54
+ for (const b of bindings) {
55
+ if (!byStage.has(b.stage))
56
+ byStage.set(b.stage, []);
57
+ byStage.get(b.stage).push(b);
58
+ }
59
+ for (const [stage, stageBindings] of byStage) {
60
+ printHeader(`Stage: ${stage}`, (msg) => this.log(msg));
61
+ printTable(stageBindings, {
62
+ serviceSlug: {
63
+ header: 'Service',
64
+ minWidth: 25,
65
+ get: (row) => chalk.cyan(row.serviceSlug),
66
+ },
67
+ serviceType: {
68
+ header: 'Type',
69
+ minWidth: 18,
70
+ get: (row) => row.serviceType || chalk.gray('-'),
71
+ },
72
+ serviceAccess: {
73
+ header: 'Access',
74
+ get: (row) => {
75
+ if (!row.serviceAccess)
76
+ return chalk.gray('-');
77
+ return row.serviceAccess === 'private' ? chalk.yellow(row.serviceAccess) : chalk.green(row.serviceAccess);
78
+ },
79
+ },
80
+ networkSlug: {
81
+ header: 'Network',
82
+ get: (row) => row.networkSlug || chalk.gray('-'),
83
+ },
84
+ }, (msg) => this.log(msg));
85
+ }
86
+ }
87
+ else {
88
+ printTable(bindings, {
89
+ serviceSlug: {
90
+ header: 'Service',
91
+ minWidth: 25,
92
+ get: (row) => chalk.cyan(row.serviceSlug),
93
+ },
94
+ serviceType: {
95
+ header: 'Type',
96
+ minWidth: 18,
97
+ get: (row) => row.serviceType || chalk.gray('-'),
98
+ },
99
+ serviceAccess: {
100
+ header: 'Access',
101
+ get: (row) => {
102
+ if (!row.serviceAccess)
103
+ return chalk.gray('-');
104
+ return row.serviceAccess === 'private' ? chalk.yellow(row.serviceAccess) : chalk.green(row.serviceAccess);
105
+ },
106
+ },
107
+ networkSlug: {
108
+ header: 'Network',
109
+ get: (row) => row.networkSlug || chalk.gray('-'),
110
+ },
111
+ createdAt: {
112
+ header: 'Created',
113
+ get: (row) => new Date(row.createdAt).toLocaleDateString(),
114
+ },
115
+ }, (msg) => this.log(msg));
116
+ }
117
+ }
118
+ catch (error) {
119
+ spinner?.fail('Failed');
120
+ const axiosError = error;
121
+ this.log(chalk.red(`\n❌ ${axiosError.response?.data?.message ?? axiosError.message}`));
122
+ this.exit(1);
123
+ }
124
+ }
125
+ }
@@ -13,11 +13,14 @@ export default class ModuleCreate extends Command {
13
13
  domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
14
14
  framework: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
15
15
  installCommand: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
+ json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
+ 'module-type': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
16
18
  name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
17
19
  runCommand: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
18
20
  runtime: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
19
21
  runtimeVersion: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
22
  slug: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
+ subdomain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
21
24
  sourceDirectory: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
22
25
  sourceLocation: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
23
26
  };