@hyperdrive.bot/cli 1.0.13 → 1.0.17
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 +4526 -780
- package/dist/commands/deploy.d.ts +18 -0
- package/dist/commands/deploy.js +239 -0
- package/dist/commands/deployment/create.js +10 -2
- package/dist/commands/domain/{switch.d.ts → set-production.d.ts} +1 -1
- package/dist/commands/domain/set-production.js +27 -0
- package/dist/commands/git/list-open-prs.d.ts +12 -0
- package/dist/commands/git/list-open-prs.js +87 -0
- package/dist/commands/hook/add.d.ts +22 -0
- package/dist/commands/hook/add.js +299 -0
- package/dist/commands/hook/list.d.ts +11 -0
- package/dist/commands/hook/list.js +111 -0
- package/dist/commands/hook/logs.d.ts +13 -0
- package/dist/commands/hook/logs.js +124 -0
- package/dist/commands/hook/remove.d.ts +12 -0
- package/dist/commands/hook/remove.js +115 -0
- package/dist/commands/hook/toggle.d.ts +12 -0
- package/dist/commands/hook/toggle.js +125 -0
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +49 -9
- package/dist/commands/module/bindings.d.ts +14 -0
- package/dist/commands/module/bindings.js +125 -0
- package/dist/commands/module/create.d.ts +3 -0
- package/dist/commands/module/create.js +156 -78
- package/dist/commands/module/list.d.ts +1 -0
- package/dist/commands/module/list.js +22 -1
- package/dist/commands/module/sync.d.ts +29 -0
- package/dist/commands/module/sync.js +409 -0
- package/dist/commands/module/unlink.d.ts +11 -0
- package/dist/commands/module/unlink.js +77 -0
- package/dist/commands/module/update.d.ts +10 -0
- package/dist/commands/module/update.js +168 -5
- package/dist/commands/network/discover.d.ts +12 -0
- package/dist/commands/network/discover.js +210 -0
- package/dist/commands/network/get.d.ts +13 -0
- package/dist/commands/network/get.js +90 -0
- package/dist/commands/{auth/logout.d.ts → network/list.d.ts} +2 -9
- package/dist/commands/network/list.js +71 -0
- package/dist/commands/network/register.d.ts +16 -0
- package/dist/commands/network/register.js +144 -0
- package/dist/commands/parameter/sync.d.ts +13 -0
- package/dist/commands/parameter/sync.js +69 -1
- package/dist/commands/project/sync.d.ts +5 -11
- package/dist/commands/project/sync.js +12 -381
- package/dist/commands/seed.d.ts +93 -0
- package/dist/commands/seed.js +324 -0
- package/dist/commands/service/backup.d.ts +17 -0
- package/dist/commands/service/backup.js +156 -0
- package/dist/commands/service/backups.d.ts +14 -0
- package/dist/commands/service/backups.js +110 -0
- package/dist/commands/service/bind.d.ts +16 -0
- package/dist/commands/service/bind.js +106 -0
- package/dist/commands/service/bindings.d.ts +13 -0
- package/dist/commands/service/bindings.js +78 -0
- package/dist/commands/service/clone.d.ts +19 -0
- package/dist/commands/service/clone.js +153 -0
- package/dist/commands/service/create.d.ts +16 -0
- package/dist/commands/service/create.js +212 -0
- package/dist/commands/service/get.d.ts +13 -0
- package/dist/commands/service/get.js +97 -0
- package/dist/commands/service/list.d.ts +12 -0
- package/dist/commands/service/list.js +86 -0
- package/dist/commands/service/register.d.ts +21 -0
- package/dist/commands/service/register.js +215 -0
- package/dist/commands/service/restore.d.ts +19 -0
- package/dist/commands/service/restore.js +158 -0
- package/dist/commands/service/seed.d.ts +17 -0
- package/dist/commands/service/seed.js +173 -0
- package/dist/commands/service/templates.d.ts +10 -0
- package/dist/commands/service/templates.js +66 -0
- package/dist/commands/service/unbind.d.ts +15 -0
- package/dist/commands/service/unbind.js +74 -0
- package/dist/commands/stage/create.d.ts +23 -0
- package/dist/commands/stage/create.js +145 -6
- package/dist/commands/stage/delete.d.ts +11 -0
- package/dist/commands/stage/delete.js +85 -0
- package/dist/commands/stage/deploy.d.ts +34 -0
- package/dist/commands/stage/deploy.js +294 -0
- package/dist/commands/stage/ensure-branches.d.ts +23 -0
- package/dist/commands/stage/ensure-branches.js +101 -0
- package/dist/commands/stage/list.js +4 -0
- package/dist/commands/stage/status.d.ts +14 -0
- package/dist/commands/stage/status.js +100 -0
- package/dist/commands/{jira → tracker}/connect.js +32 -23
- package/dist/commands/tracker/hook/add.d.ts +25 -0
- package/dist/commands/tracker/hook/add.js +284 -0
- package/dist/commands/{jira → tracker}/hook/list.js +20 -11
- package/dist/commands/{jira/hook/add.d.ts → tracker/hook/logs.d.ts} +2 -3
- package/dist/commands/tracker/hook/logs.js +126 -0
- package/dist/commands/{jira → tracker}/hook/remove.js +9 -8
- package/dist/commands/{jira → tracker}/hook/toggle.js +14 -12
- package/dist/commands/tracker/project/init.d.ts +17 -0
- package/dist/commands/tracker/project/init.js +178 -0
- package/dist/commands/tracker/project/link-module.d.ts +17 -0
- package/dist/commands/tracker/project/link-module.js +287 -0
- package/dist/commands/tracker/project/list-modules.d.ts +11 -0
- package/dist/commands/tracker/project/list-modules.js +117 -0
- package/dist/commands/tracker/project/list.d.ts +10 -0
- package/dist/commands/tracker/project/list.js +90 -0
- package/dist/commands/tracker/project/status.d.ts +13 -0
- package/dist/commands/tracker/project/status.js +168 -0
- package/dist/commands/tracker/project/unlink-module.d.ts +13 -0
- package/dist/commands/tracker/project/unlink-module.js +251 -0
- package/dist/commands/{jira → tracker}/status.js +3 -3
- package/dist/lib/ensure-branches.d.ts +53 -0
- package/dist/lib/ensure-branches.js +149 -0
- package/dist/lib/git-providers/github.d.ts +16 -0
- package/dist/lib/git-providers/github.js +157 -0
- package/dist/lib/git-providers/gitlab.d.ts +16 -0
- package/dist/lib/git-providers/gitlab.js +148 -0
- package/dist/lib/git-providers/index.d.ts +67 -0
- package/dist/lib/git-providers/index.js +39 -0
- package/dist/lib/lambda-warmer.d.ts +106 -0
- package/dist/lib/lambda-warmer.js +189 -0
- package/dist/services/hyperdrive-sigv4.d.ts +359 -5
- package/dist/services/hyperdrive-sigv4.js +177 -12
- package/dist/utils/hook-flow.d.ts +60 -3
- package/dist/utils/hook-flow.js +437 -2
- package/dist/utils/hook-normalize.d.ts +6 -0
- package/dist/utils/hook-normalize.js +33 -0
- package/dist/utils/lifecycle-poller.d.ts +32 -0
- package/dist/utils/lifecycle-poller.js +72 -0
- package/dist/utils/retry.d.ts +43 -0
- package/dist/utils/retry.js +88 -0
- package/dist/utils/summary-display.js +1 -1
- package/dist/utils/tracker-project-flow.d.ts +84 -0
- package/dist/utils/tracker-project-flow.js +564 -0
- package/package.json +41 -13
- package/dist/commands/auth/login.d.ts +0 -16
- package/dist/commands/auth/login.js +0 -179
- package/dist/commands/auth/logout.js +0 -116
- package/dist/commands/auth/refresh.d.ts +0 -6
- package/dist/commands/auth/refresh.js +0 -66
- package/dist/commands/auth/status.d.ts +0 -6
- package/dist/commands/auth/status.js +0 -63
- package/dist/commands/config/get.d.ts +0 -9
- package/dist/commands/config/get.js +0 -37
- package/dist/commands/config/set.d.ts +0 -10
- package/dist/commands/config/set.js +0 -48
- package/dist/commands/config/show.d.ts +0 -6
- package/dist/commands/config/show.js +0 -10
- package/dist/commands/domain/current.d.ts +0 -6
- package/dist/commands/domain/current.js +0 -18
- package/dist/commands/domain/list.d.ts +0 -6
- package/dist/commands/domain/list.js +0 -42
- package/dist/commands/domain/switch.js +0 -40
- package/dist/commands/jira/hook/add.js +0 -147
- package/dist/services/tenant-service.d.ts +0 -127
- package/dist/services/tenant-service.js +0 -396
- package/dist/utils/auth-flow.d.ts +0 -147
- package/dist/utils/auth-flow.js +0 -479
- package/oclif.manifest.json +0 -3519
- /package/dist/commands/{jira → tracker}/connect.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/list.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/remove.d.ts +0 -0
- /package/dist/commands/{jira → tracker}/hook/toggle.d.ts +0 -0
- /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
|
+
}
|
package/dist/commands/init.d.ts
CHANGED
|
@@ -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: {
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|