@hyperdrive.bot/cli 1.0.7 → 1.0.8
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 +298 -56
- package/dist/commands/account/list.d.ts +3 -0
- package/dist/commands/account/list.js +9 -2
- package/dist/commands/git/connect.js +1 -0
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.js +20 -19
- package/dist/commands/jira/connect.d.ts +1 -0
- package/dist/commands/jira/connect.js +17 -6
- package/dist/commands/jira/hook/add.d.ts +17 -0
- package/dist/commands/jira/hook/add.js +147 -0
- package/dist/commands/jira/hook/list.d.ts +14 -0
- package/dist/commands/jira/hook/list.js +105 -0
- package/dist/commands/jira/hook/remove.d.ts +15 -0
- package/dist/commands/jira/hook/remove.js +119 -0
- package/dist/commands/jira/hook/toggle.d.ts +15 -0
- package/dist/commands/jira/hook/toggle.js +136 -0
- package/dist/commands/project/init.d.ts +21 -0
- package/dist/commands/project/init.js +576 -0
- package/dist/commands/project/list.d.ts +10 -0
- package/dist/commands/project/list.js +119 -0
- package/dist/commands/project/status.d.ts +13 -0
- package/dist/commands/project/status.js +163 -0
- package/dist/commands/project/sync.d.ts +26 -0
- package/dist/commands/project/sync.js +388 -0
- package/dist/services/hyperdrive-sigv4.d.ts +125 -0
- package/dist/services/hyperdrive-sigv4.js +45 -0
- package/dist/utils/account-flow.d.ts +2 -2
- package/dist/utils/account-flow.js +4 -4
- package/dist/utils/git-flow.d.ts +1 -0
- package/dist/utils/git-flow.js +2 -2
- package/dist/utils/hook-flow.d.ts +21 -0
- package/dist/utils/hook-flow.js +154 -0
- package/dist/utils/jira-flow.d.ts +2 -2
- package/dist/utils/jira-flow.js +4 -4
- package/oclif.manifest.json +590 -128
- package/package.json +5 -1
|
@@ -0,0 +1,119 @@
|
|
|
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 { promptConfirmDelete, promptSelectHook } from '../../../utils/hook-flow.js';
|
|
6
|
+
export default class HookRemove extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
project: Args.string({ description: 'Hyperdrive project ID or slug', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Remove a status transition hook from a Jira-linked project';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> jira hook remove my-project',
|
|
13
|
+
'<%= config.bin %> jira hook remove my-project --hook-id hook-123',
|
|
14
|
+
'<%= config.bin %> jira hook remove my-project --hook-id hook-123 --json',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
domain: Flags.string({
|
|
18
|
+
char: 'd',
|
|
19
|
+
description: 'Hyperdrive tenant domain',
|
|
20
|
+
}),
|
|
21
|
+
'hook-id': Flags.string({
|
|
22
|
+
description: 'Hook ID to remove (skips interactive selection)',
|
|
23
|
+
}),
|
|
24
|
+
json: Flags.boolean({
|
|
25
|
+
description: 'Output raw JSON',
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(HookRemove);
|
|
30
|
+
const isJson = flags.json;
|
|
31
|
+
const hookIdFlag = flags['hook-id'];
|
|
32
|
+
// Authenticate
|
|
33
|
+
let apiService;
|
|
34
|
+
const spinner = isJson ? null : ora('Checking authentication...').start();
|
|
35
|
+
try {
|
|
36
|
+
apiService = new HyperdriveSigV4Service(flags.domain);
|
|
37
|
+
spinner?.succeed('Authenticated');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner?.fail('Not authenticated');
|
|
41
|
+
this.error(`${error.message}\n\n` +
|
|
42
|
+
`Please authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
43
|
+
}
|
|
44
|
+
let hookId;
|
|
45
|
+
if (hookIdFlag) {
|
|
46
|
+
hookId = hookIdFlag;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (isJson) {
|
|
50
|
+
this.error('Non-interactive mode requires --hook-id flag with --json');
|
|
51
|
+
}
|
|
52
|
+
// Fetch hooks for interactive selection
|
|
53
|
+
const fetchSpinner = ora('Fetching hooks...').start();
|
|
54
|
+
try {
|
|
55
|
+
const response = await apiService.hookList(args.project);
|
|
56
|
+
const hooks = response.hooks;
|
|
57
|
+
fetchSpinner.succeed(`Found ${hooks.length} hook${hooks.length === 1 ? '' : 's'}`);
|
|
58
|
+
if (hooks.length === 0) {
|
|
59
|
+
this.log('');
|
|
60
|
+
this.log(chalk.yellow('No hooks configured for this project'));
|
|
61
|
+
this.log('');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.log('');
|
|
65
|
+
const selectedHook = await promptSelectHook(hooks);
|
|
66
|
+
hookId = selectedHook.hookId;
|
|
67
|
+
const confirmed = await promptConfirmDelete(selectedHook);
|
|
68
|
+
if (!confirmed) {
|
|
69
|
+
this.log(chalk.dim('Cancelled'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
fetchSpinner.fail('Failed to fetch hooks');
|
|
75
|
+
this.handleApiError(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Delete hook
|
|
79
|
+
const deleteSpinner = isJson ? null : ora('Deleting hook...').start();
|
|
80
|
+
try {
|
|
81
|
+
const result = await apiService.hookDelete(args.project, hookId);
|
|
82
|
+
deleteSpinner?.succeed('Hook deleted');
|
|
83
|
+
if (isJson) {
|
|
84
|
+
this.log(JSON.stringify({ hookId, ...result }, null, 2));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
this.log('');
|
|
88
|
+
this.log(chalk.green('✅ Hook deleted successfully'));
|
|
89
|
+
this.log('');
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
deleteSpinner?.fail('Failed to delete hook');
|
|
93
|
+
this.handleApiError(error);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
handleApiError(error) {
|
|
97
|
+
let errorMessage = error.message;
|
|
98
|
+
if (error.response) {
|
|
99
|
+
const status = error.response.status;
|
|
100
|
+
const data = error.response.data;
|
|
101
|
+
if (status === 401) {
|
|
102
|
+
errorMessage = 'Authentication failed — please run "hd auth login"';
|
|
103
|
+
}
|
|
104
|
+
else if (status === 403) {
|
|
105
|
+
errorMessage = 'Access denied — check your permissions';
|
|
106
|
+
}
|
|
107
|
+
else if (status === 404) {
|
|
108
|
+
errorMessage = 'Hook or project not found — verify the IDs';
|
|
109
|
+
}
|
|
110
|
+
else if (data?.error) {
|
|
111
|
+
errorMessage = data.error;
|
|
112
|
+
}
|
|
113
|
+
else if (data?.message) {
|
|
114
|
+
errorMessage = data.message;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
this.error(errorMessage);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class HookToggle extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
project: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
'hook-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
private handleApiError;
|
|
15
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
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 { promptSelectHook } from '../../../utils/hook-flow.js';
|
|
6
|
+
export default class HookToggle extends Command {
|
|
7
|
+
static args = {
|
|
8
|
+
project: Args.string({ description: 'Hyperdrive project ID or slug', required: true }),
|
|
9
|
+
};
|
|
10
|
+
static description = 'Toggle the enabled state of a status transition hook';
|
|
11
|
+
static examples = [
|
|
12
|
+
'<%= config.bin %> jira hook toggle my-project',
|
|
13
|
+
'<%= config.bin %> jira hook toggle my-project --hook-id hook-123',
|
|
14
|
+
'<%= config.bin %> jira hook toggle my-project --hook-id hook-123 --json',
|
|
15
|
+
];
|
|
16
|
+
static flags = {
|
|
17
|
+
domain: Flags.string({
|
|
18
|
+
char: 'd',
|
|
19
|
+
description: 'Hyperdrive tenant domain',
|
|
20
|
+
}),
|
|
21
|
+
'hook-id': Flags.string({
|
|
22
|
+
description: 'Hook ID to toggle (skips interactive selection)',
|
|
23
|
+
}),
|
|
24
|
+
json: Flags.boolean({
|
|
25
|
+
description: 'Output raw JSON',
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(HookToggle);
|
|
30
|
+
const isJson = flags.json;
|
|
31
|
+
const hookIdFlag = flags['hook-id'];
|
|
32
|
+
// Authenticate
|
|
33
|
+
let apiService;
|
|
34
|
+
const spinner = isJson ? null : ora('Checking authentication...').start();
|
|
35
|
+
try {
|
|
36
|
+
apiService = new HyperdriveSigV4Service(flags.domain);
|
|
37
|
+
spinner?.succeed('Authenticated');
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
spinner?.fail('Not authenticated');
|
|
41
|
+
this.error(`${error.message}\n\n` +
|
|
42
|
+
`Please authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
43
|
+
}
|
|
44
|
+
let hookId;
|
|
45
|
+
let currentEnabled;
|
|
46
|
+
if (hookIdFlag) {
|
|
47
|
+
hookId = hookIdFlag;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
if (isJson) {
|
|
51
|
+
this.error('Non-interactive mode requires --hook-id flag with --json');
|
|
52
|
+
}
|
|
53
|
+
// Fetch hooks for interactive selection
|
|
54
|
+
const fetchSpinner = ora('Fetching hooks...').start();
|
|
55
|
+
try {
|
|
56
|
+
const response = await apiService.hookList(args.project);
|
|
57
|
+
const hooks = response.hooks;
|
|
58
|
+
fetchSpinner.succeed(`Found ${hooks.length} hook${hooks.length === 1 ? '' : 's'}`);
|
|
59
|
+
if (hooks.length === 0) {
|
|
60
|
+
this.log('');
|
|
61
|
+
this.log(chalk.yellow('No hooks configured for this project'));
|
|
62
|
+
this.log('');
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
this.log('');
|
|
66
|
+
const selectedHook = await promptSelectHook(hooks);
|
|
67
|
+
hookId = selectedHook.hookId;
|
|
68
|
+
currentEnabled = selectedHook.enabled;
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
fetchSpinner.fail('Failed to fetch hooks');
|
|
72
|
+
this.handleApiError(error);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Toggle hook
|
|
76
|
+
const toggleSpinner = isJson ? null : ora('Toggling hook...').start();
|
|
77
|
+
try {
|
|
78
|
+
// If we know current state from selection, flip it. Otherwise let the API fetch + toggle.
|
|
79
|
+
const newEnabled = currentEnabled !== undefined ? !currentEnabled : undefined;
|
|
80
|
+
// If we don't know the current state (non-interactive with --hook-id), fetch it first
|
|
81
|
+
let enabled;
|
|
82
|
+
if (newEnabled === undefined) {
|
|
83
|
+
const response = await apiService.hookList(args.project);
|
|
84
|
+
const hook = response.hooks.find(h => h.hookId === hookId);
|
|
85
|
+
if (!hook) {
|
|
86
|
+
toggleSpinner?.fail('Hook not found');
|
|
87
|
+
this.error(`Hook ${hookId} not found in project ${args.project}`);
|
|
88
|
+
}
|
|
89
|
+
enabled = !hook.enabled;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
enabled = newEnabled;
|
|
93
|
+
}
|
|
94
|
+
const updatedHook = await apiService.hookUpdate(args.project, hookId, { enabled });
|
|
95
|
+
toggleSpinner?.succeed('Hook toggled');
|
|
96
|
+
if (isJson) {
|
|
97
|
+
this.log(JSON.stringify(updatedHook, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
this.log('');
|
|
101
|
+
this.log(chalk.green('✅ Hook updated'));
|
|
102
|
+
this.log(` Hook ID: ${chalk.cyan(updatedHook.hookId)}`);
|
|
103
|
+
this.log(` Trigger Status: ${chalk.cyan(updatedHook.triggerStatus)}`);
|
|
104
|
+
this.log(` Action Type: ${chalk.cyan(updatedHook.actionType)}`);
|
|
105
|
+
this.log(` Enabled: ${updatedHook.enabled ? chalk.green('enabled') : chalk.red('disabled')}`);
|
|
106
|
+
this.log('');
|
|
107
|
+
}
|
|
108
|
+
catch (error) {
|
|
109
|
+
toggleSpinner?.fail('Failed to toggle hook');
|
|
110
|
+
this.handleApiError(error);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
handleApiError(error) {
|
|
114
|
+
let errorMessage = error.message;
|
|
115
|
+
if (error.response) {
|
|
116
|
+
const status = error.response.status;
|
|
117
|
+
const data = error.response.data;
|
|
118
|
+
if (status === 401) {
|
|
119
|
+
errorMessage = 'Authentication failed — please run "hd auth login"';
|
|
120
|
+
}
|
|
121
|
+
else if (status === 403) {
|
|
122
|
+
errorMessage = 'Access denied — check your permissions';
|
|
123
|
+
}
|
|
124
|
+
else if (status === 404) {
|
|
125
|
+
errorMessage = 'Hook or project not found — verify the IDs';
|
|
126
|
+
}
|
|
127
|
+
else if (data?.error) {
|
|
128
|
+
errorMessage = data.error;
|
|
129
|
+
}
|
|
130
|
+
else if (data?.message) {
|
|
131
|
+
errorMessage = data.message;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
this.error(errorMessage);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ProjectInit 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
|
+
'jira-key': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
name: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
project: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
repo: import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
'status-map': import("@oclif/core/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
private addRepos;
|
|
16
|
+
private configureStatusMapping;
|
|
17
|
+
private confirmAndFinalize;
|
|
18
|
+
private deriveRepoName;
|
|
19
|
+
private getJiraProjectKey;
|
|
20
|
+
private selectOrCreateProject;
|
|
21
|
+
}
|