@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.
- package/README.md +1495 -474
- 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 +360 -5
- package/dist/services/hyperdrive-sigv4.js +192 -24
- 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 +35 -7
- 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,299 @@
|
|
|
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
|
+
import { promptLifecycleEvent, promptLifecycleConditions, promptActionTypeV2, promptActionConfig, promptOrder, promptTriggerType, promptCronExpression, promptCronTimezone, promptFlexibleWindow, getActionCategory, VALID_LIFECYCLE_EVENTS, } from '../../utils/hook-flow.js';
|
|
7
|
+
export default class HookAdd extends Command {
|
|
8
|
+
static description = 'Create a tenant lifecycle hook or cron-triggered hook';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> hook add',
|
|
11
|
+
'<%= config.bin %> hook add --event deploy.completed --action-type slack-notify --action-config \'{"channel":"C0ABC"}\'',
|
|
12
|
+
'<%= config.bin %> hook add --cron "0 3 * * ? *" --timezone "America/Sao_Paulo" --action-type webhook --action-config \'{"url":"https://example.com/hook","method":"POST"}\'',
|
|
13
|
+
'<%= config.bin %> hook add --cron "rate(1 hour)" --action-type slack-notify --action-config \'{"channel":"C0ABC"}\'',
|
|
14
|
+
];
|
|
15
|
+
static flags = {
|
|
16
|
+
'action-config': Flags.string({
|
|
17
|
+
description: 'Action config as JSON string',
|
|
18
|
+
}),
|
|
19
|
+
'action-type': Flags.string({
|
|
20
|
+
description: 'Action type (slack-notify, webhook, ci-trigger, etc.)',
|
|
21
|
+
}),
|
|
22
|
+
conditions: Flags.string({
|
|
23
|
+
description: 'Trigger conditions as JSON string (lifecycle hooks only)',
|
|
24
|
+
}),
|
|
25
|
+
cron: Flags.string({
|
|
26
|
+
description: 'Cron expression or rate expression (e.g. "0 3 * * ? *", "rate(1 hour)"). Minimum interval: 5 minutes.',
|
|
27
|
+
exclusive: ['event', 'conditions'],
|
|
28
|
+
}),
|
|
29
|
+
domain: Flags.string({
|
|
30
|
+
char: 'd',
|
|
31
|
+
description: 'Hyperdrive tenant domain',
|
|
32
|
+
}),
|
|
33
|
+
event: Flags.string({
|
|
34
|
+
description: 'Lifecycle event (e.g., deploy.completed, stage.provisioned)',
|
|
35
|
+
exclusive: ['cron', 'timezone', 'flexible-window'],
|
|
36
|
+
}),
|
|
37
|
+
'flexible-window': Flags.integer({
|
|
38
|
+
description: 'Flexible time window in minutes for cron hooks (default: 15, max: 1440)',
|
|
39
|
+
dependsOn: ['cron'],
|
|
40
|
+
}),
|
|
41
|
+
json: Flags.boolean({
|
|
42
|
+
description: 'Output raw JSON',
|
|
43
|
+
}),
|
|
44
|
+
order: Flags.integer({
|
|
45
|
+
description: 'Execution order (lower runs first)',
|
|
46
|
+
}),
|
|
47
|
+
timezone: Flags.string({
|
|
48
|
+
description: 'IANA timezone for cron expression (default: UTC). E.g. "America/Sao_Paulo"',
|
|
49
|
+
dependsOn: ['cron'],
|
|
50
|
+
}),
|
|
51
|
+
};
|
|
52
|
+
async run() {
|
|
53
|
+
const { flags } = await this.parse(HookAdd);
|
|
54
|
+
const isJson = flags.json ?? false;
|
|
55
|
+
// Authenticate
|
|
56
|
+
let apiService;
|
|
57
|
+
const spinner = isJson ? null : ora('Checking authentication...').start();
|
|
58
|
+
try {
|
|
59
|
+
apiService = new HyperdriveSigV4Service(flags.domain);
|
|
60
|
+
spinner?.succeed('Authenticated');
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
spinner?.fail('Not authenticated');
|
|
64
|
+
this.error(`${error.message}\n\n` +
|
|
65
|
+
`Please authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
66
|
+
}
|
|
67
|
+
// Detect interactive vs non-interactive
|
|
68
|
+
const hasNonInteractiveFlags = flags.event || flags['action-type'] || flags.cron;
|
|
69
|
+
if (isJson && !hasNonInteractiveFlags) {
|
|
70
|
+
this.error('Interactive mode not supported with --json. Provide --event (or --cron) and --action-type flags.');
|
|
71
|
+
}
|
|
72
|
+
if (hasNonInteractiveFlags) {
|
|
73
|
+
await this.runNonInteractive(apiService, flags, isJson);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await this.runInteractive(apiService);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
handleApiError(error) {
|
|
80
|
+
let errorMessage = error.message;
|
|
81
|
+
if (error.response) {
|
|
82
|
+
const status = error.response.status;
|
|
83
|
+
const data = error.response.data;
|
|
84
|
+
if (status === 401) {
|
|
85
|
+
errorMessage = 'Authentication failed — please run "hd auth login"';
|
|
86
|
+
}
|
|
87
|
+
else if (status === 403) {
|
|
88
|
+
errorMessage = 'Access denied — check your permissions';
|
|
89
|
+
}
|
|
90
|
+
else if (status === 404) {
|
|
91
|
+
errorMessage = 'Not found';
|
|
92
|
+
}
|
|
93
|
+
else if (data?.error) {
|
|
94
|
+
errorMessage = data.error;
|
|
95
|
+
}
|
|
96
|
+
else if (data?.message) {
|
|
97
|
+
errorMessage = data.message;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
this.error(errorMessage);
|
|
101
|
+
}
|
|
102
|
+
printSuccess(hook, trigger, actionType, category, order) {
|
|
103
|
+
this.log('');
|
|
104
|
+
this.log(chalk.green('Hook created successfully'));
|
|
105
|
+
this.log('');
|
|
106
|
+
this.log(` Hook ID: ${chalk.cyan(hook.hookId)}`);
|
|
107
|
+
if ('type' in trigger && trigger.type === 'cron') {
|
|
108
|
+
this.log(` Trigger: ${chalk.cyan('cron')}`);
|
|
109
|
+
this.log(` Expression: ${chalk.cyan(trigger.expression)}`);
|
|
110
|
+
this.log(` Timezone: ${chalk.cyan(trigger.timezone)}`);
|
|
111
|
+
if (trigger.flexibleWindowMinutes !== undefined) {
|
|
112
|
+
this.log(` Flex Window: ${chalk.cyan(String(trigger.flexibleWindowMinutes))} min`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
else if ('event' in trigger) {
|
|
116
|
+
this.log(` Event: ${chalk.cyan(trigger.event)}`);
|
|
117
|
+
if (trigger.conditions && Object.keys(trigger.conditions).length > 0) {
|
|
118
|
+
this.log(` Conditions: ${chalk.cyan(JSON.stringify(trigger.conditions))}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
this.log(` Action Type: ${chalk.cyan(actionType)}`);
|
|
122
|
+
this.log(` Category: ${chalk.cyan(category)}`);
|
|
123
|
+
if (order !== undefined) {
|
|
124
|
+
this.log(` Order: ${chalk.cyan(String(order))}`);
|
|
125
|
+
}
|
|
126
|
+
this.log(` Enabled: ${hook.enabled ? chalk.green('yes') : chalk.red('no')}`);
|
|
127
|
+
this.log(` Created: ${chalk.dim(hook.createdAt)}`);
|
|
128
|
+
this.log('');
|
|
129
|
+
}
|
|
130
|
+
async runInteractive(apiService) {
|
|
131
|
+
this.log('');
|
|
132
|
+
// Step 1: Choose trigger type
|
|
133
|
+
const triggerType = await promptTriggerType();
|
|
134
|
+
let trigger;
|
|
135
|
+
if (triggerType === 'cron') {
|
|
136
|
+
// Cron trigger flow
|
|
137
|
+
const expression = await promptCronExpression();
|
|
138
|
+
const timezone = await promptCronTimezone();
|
|
139
|
+
const flexibleWindowMinutes = await promptFlexibleWindow();
|
|
140
|
+
trigger = {
|
|
141
|
+
expression,
|
|
142
|
+
timezone,
|
|
143
|
+
type: 'cron',
|
|
144
|
+
...(flexibleWindowMinutes !== undefined && { flexibleWindowMinutes }),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Lifecycle trigger flow
|
|
149
|
+
const event = await promptLifecycleEvent();
|
|
150
|
+
const conditions = await promptLifecycleConditions(event);
|
|
151
|
+
trigger = {
|
|
152
|
+
conditions: Object.keys(conditions).length > 0 ? conditions : undefined,
|
|
153
|
+
event,
|
|
154
|
+
source: 'hyperdrive',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
// Step 2: Action type (grouped)
|
|
158
|
+
const { category, type: actionType } = await promptActionTypeV2();
|
|
159
|
+
// Step 3: Action config
|
|
160
|
+
const actionConfig = await promptActionConfig(actionType);
|
|
161
|
+
// Step 4: Order
|
|
162
|
+
const order = await promptOrder();
|
|
163
|
+
// Confirmation summary
|
|
164
|
+
this.log('');
|
|
165
|
+
this.log(chalk.bold('Summary:'));
|
|
166
|
+
if ('type' in trigger && trigger.type === 'cron') {
|
|
167
|
+
this.log(` Trigger: ${chalk.cyan('cron')}`);
|
|
168
|
+
this.log(` Expression: ${chalk.cyan(trigger.expression)}`);
|
|
169
|
+
this.log(` Timezone: ${chalk.cyan(trigger.timezone)}`);
|
|
170
|
+
if (trigger.flexibleWindowMinutes !== undefined) {
|
|
171
|
+
this.log(` Flex Window: ${chalk.cyan(String(trigger.flexibleWindowMinutes))} min`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else if ('event' in trigger) {
|
|
175
|
+
this.log(` Event: ${chalk.cyan(trigger.event)}`);
|
|
176
|
+
if (trigger.conditions && Object.keys(trigger.conditions).length > 0) {
|
|
177
|
+
this.log(` Conditions: ${chalk.cyan(JSON.stringify(trigger.conditions))}`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
this.log(` Action Type: ${chalk.cyan(actionType)}`);
|
|
181
|
+
this.log(` Category: ${chalk.cyan(category)}`);
|
|
182
|
+
if (order !== undefined) {
|
|
183
|
+
this.log(` Order: ${chalk.cyan(String(order))}`);
|
|
184
|
+
}
|
|
185
|
+
this.log('');
|
|
186
|
+
const { confirmed } = await inquirer.prompt([{
|
|
187
|
+
type: 'confirm',
|
|
188
|
+
name: 'confirmed',
|
|
189
|
+
message: 'Create this hook?',
|
|
190
|
+
default: true,
|
|
191
|
+
}]);
|
|
192
|
+
if (!confirmed) {
|
|
193
|
+
this.log(chalk.yellow('Cancelled'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
// Build request
|
|
197
|
+
const body = {
|
|
198
|
+
action: { category, config: actionConfig, type: actionType },
|
|
199
|
+
trigger,
|
|
200
|
+
};
|
|
201
|
+
if (order !== undefined)
|
|
202
|
+
body.order = order;
|
|
203
|
+
const createSpinner = ora('Creating hook...').start();
|
|
204
|
+
try {
|
|
205
|
+
const hook = await apiService.tenantHookCreate(body);
|
|
206
|
+
createSpinner.succeed('Hook created');
|
|
207
|
+
this.printSuccess(hook, trigger, actionType, category, order);
|
|
208
|
+
}
|
|
209
|
+
catch (error) {
|
|
210
|
+
createSpinner.fail('Failed to create hook');
|
|
211
|
+
this.handleApiError(error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
async runNonInteractive(apiService, flags, isJson) {
|
|
215
|
+
const isCron = !!flags.cron;
|
|
216
|
+
let trigger;
|
|
217
|
+
if (isCron) {
|
|
218
|
+
// Build cron trigger
|
|
219
|
+
const expression = flags.cron;
|
|
220
|
+
const timezone = flags.timezone ?? 'UTC';
|
|
221
|
+
const flexibleWindowMinutes = flags['flexible-window'];
|
|
222
|
+
if (timezone === 'UTC' && !flags.timezone && !isJson) {
|
|
223
|
+
this.warn('No --timezone specified, defaulting to UTC. Consider using --timezone to set your local timezone.');
|
|
224
|
+
}
|
|
225
|
+
if (flexibleWindowMinutes !== undefined && (flexibleWindowMinutes < 0 || flexibleWindowMinutes > 1440)) {
|
|
226
|
+
this.error('--flexible-window must be between 0 and 1440 minutes');
|
|
227
|
+
}
|
|
228
|
+
trigger = {
|
|
229
|
+
expression,
|
|
230
|
+
timezone,
|
|
231
|
+
type: 'cron',
|
|
232
|
+
...(flexibleWindowMinutes !== undefined && { flexibleWindowMinutes }),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
// Lifecycle trigger
|
|
237
|
+
if (!flags.event) {
|
|
238
|
+
this.error('--event is required for lifecycle hooks (or use --cron for cron-triggered hooks)');
|
|
239
|
+
}
|
|
240
|
+
const event = flags.event;
|
|
241
|
+
if (!VALID_LIFECYCLE_EVENTS.includes(event)) {
|
|
242
|
+
this.error(`Invalid lifecycle event: ${event}. Must be one of:\n ${VALID_LIFECYCLE_EVENTS.join('\n ')}`);
|
|
243
|
+
}
|
|
244
|
+
// Parse optional conditions
|
|
245
|
+
let conditions;
|
|
246
|
+
if (flags.conditions) {
|
|
247
|
+
try {
|
|
248
|
+
conditions = JSON.parse(flags.conditions);
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
this.error('Invalid JSON in --conditions flag');
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
trigger = {
|
|
255
|
+
conditions,
|
|
256
|
+
event,
|
|
257
|
+
source: 'hyperdrive',
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
// Validate action type
|
|
261
|
+
if (!flags['action-type']) {
|
|
262
|
+
this.error('--action-type is required in non-interactive mode');
|
|
263
|
+
}
|
|
264
|
+
const actionType = flags['action-type'];
|
|
265
|
+
// Parse optional action config
|
|
266
|
+
let actionConfig = {};
|
|
267
|
+
if (flags['action-config']) {
|
|
268
|
+
try {
|
|
269
|
+
actionConfig = JSON.parse(flags['action-config']);
|
|
270
|
+
}
|
|
271
|
+
catch {
|
|
272
|
+
this.error('Invalid JSON in --action-config flag');
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
const category = getActionCategory(actionType);
|
|
276
|
+
const order = flags.order;
|
|
277
|
+
// Build request
|
|
278
|
+
const body = {
|
|
279
|
+
action: { category, config: actionConfig, type: actionType },
|
|
280
|
+
trigger,
|
|
281
|
+
};
|
|
282
|
+
if (order !== undefined)
|
|
283
|
+
body.order = order;
|
|
284
|
+
const createSpinner = isJson ? null : ora('Creating hook...').start();
|
|
285
|
+
try {
|
|
286
|
+
const hook = await apiService.tenantHookCreate(body);
|
|
287
|
+
createSpinner?.succeed('Hook created');
|
|
288
|
+
if (isJson) {
|
|
289
|
+
this.log(JSON.stringify(hook, null, 2));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
this.printSuccess(hook, trigger, actionType, category, order);
|
|
293
|
+
}
|
|
294
|
+
catch (error) {
|
|
295
|
+
createSpinner?.fail('Failed to create hook');
|
|
296
|
+
this.handleApiError(error);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class HookList 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
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
private handleApiError;
|
|
11
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
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 { printTable } from '../../utils/table.js';
|
|
6
|
+
export default class HookList extends Command {
|
|
7
|
+
static description = 'List tenant lifecycle hooks';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> hook list',
|
|
10
|
+
'<%= config.bin %> hook list --json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
domain: Flags.string({
|
|
14
|
+
char: 'd',
|
|
15
|
+
description: 'Hyperdrive tenant domain',
|
|
16
|
+
}),
|
|
17
|
+
json: Flags.boolean({
|
|
18
|
+
description: 'Output raw JSON',
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
async run() {
|
|
22
|
+
const { flags } = await this.parse(HookList);
|
|
23
|
+
const isJson = flags.json;
|
|
24
|
+
// Authenticate
|
|
25
|
+
let apiService;
|
|
26
|
+
const spinner = isJson ? null : ora('Checking authentication...').start();
|
|
27
|
+
try {
|
|
28
|
+
apiService = new HyperdriveSigV4Service(flags.domain);
|
|
29
|
+
spinner?.succeed('Authenticated');
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
spinner?.fail('Not authenticated');
|
|
33
|
+
this.error(`${error.message}\n\n` +
|
|
34
|
+
`Please authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
35
|
+
}
|
|
36
|
+
// Fetch hooks
|
|
37
|
+
const fetchSpinner = isJson ? null : ora('Fetching hooks...').start();
|
|
38
|
+
try {
|
|
39
|
+
const hooks = await apiService.tenantHookList();
|
|
40
|
+
fetchSpinner?.succeed(`Found ${hooks.length} hook${hooks.length === 1 ? '' : 's'}`);
|
|
41
|
+
if (isJson) {
|
|
42
|
+
this.log(JSON.stringify(hooks, null, 2));
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (hooks.length === 0) {
|
|
46
|
+
this.log('');
|
|
47
|
+
this.log(chalk.yellow('No tenant hooks found.'));
|
|
48
|
+
this.log(chalk.dim(`Run ${chalk.cyan('hd hook add')} to create one.`));
|
|
49
|
+
this.log('');
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
this.log('');
|
|
53
|
+
printTable(hooks.map((h) => {
|
|
54
|
+
const isCron = h.trigger?.type === 'cron';
|
|
55
|
+
return {
|
|
56
|
+
actionType: h.action?.type ?? '',
|
|
57
|
+
createdAt: h.createdAt,
|
|
58
|
+
cronSchedule: isCron
|
|
59
|
+
? `${h.trigger?.expression ?? ''} (${h.trigger?.timezone ?? 'UTC'})`
|
|
60
|
+
: '',
|
|
61
|
+
enabled: h.enabled,
|
|
62
|
+
hookId: h.hookId,
|
|
63
|
+
order: h.order ?? '',
|
|
64
|
+
triggerEvent: isCron ? 'cron' : (h.trigger?.event ?? ''),
|
|
65
|
+
};
|
|
66
|
+
}), {
|
|
67
|
+
hookId: { header: 'Hook ID' },
|
|
68
|
+
triggerEvent: { header: 'Event' },
|
|
69
|
+
cronSchedule: { header: 'Schedule' },
|
|
70
|
+
actionType: { header: 'Action Type' },
|
|
71
|
+
enabled: {
|
|
72
|
+
get: (row) => row.enabled ? chalk.green('enabled') : chalk.red('disabled'),
|
|
73
|
+
header: 'Enabled',
|
|
74
|
+
},
|
|
75
|
+
order: { header: 'Order' },
|
|
76
|
+
createdAt: {
|
|
77
|
+
get: (row) => new Date(row.createdAt).toLocaleDateString(),
|
|
78
|
+
header: 'Created',
|
|
79
|
+
},
|
|
80
|
+
}, (msg) => this.log(msg));
|
|
81
|
+
this.log('');
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
fetchSpinner?.fail('Failed to fetch hooks');
|
|
85
|
+
this.handleApiError(error);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
handleApiError(error) {
|
|
89
|
+
let errorMessage = error.message;
|
|
90
|
+
if (error.response) {
|
|
91
|
+
const status = error.response.status;
|
|
92
|
+
const data = error.response.data;
|
|
93
|
+
if (status === 401) {
|
|
94
|
+
errorMessage = 'Authentication failed — please run "hd auth login"';
|
|
95
|
+
}
|
|
96
|
+
else if (status === 403) {
|
|
97
|
+
errorMessage = 'Access denied — check your permissions';
|
|
98
|
+
}
|
|
99
|
+
else if (status === 404) {
|
|
100
|
+
errorMessage = 'Not found';
|
|
101
|
+
}
|
|
102
|
+
else if (data?.error) {
|
|
103
|
+
errorMessage = data.error;
|
|
104
|
+
}
|
|
105
|
+
else if (data?.message) {
|
|
106
|
+
errorMessage = data.message;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.error(errorMessage);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class HookLogs 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
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
status: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
private handleApiError;
|
|
13
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { printTable } from '../../utils/table.js';
|
|
6
|
+
export default class HookLogs extends Command {
|
|
7
|
+
static description = 'View tenant lifecycle hook execution logs';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> hook logs',
|
|
10
|
+
'<%= config.bin %> hook logs --status failed --limit 5',
|
|
11
|
+
'<%= config.bin %> hook logs --json',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
domain: Flags.string({
|
|
15
|
+
char: 'd',
|
|
16
|
+
description: 'Hyperdrive tenant domain',
|
|
17
|
+
}),
|
|
18
|
+
json: Flags.boolean({
|
|
19
|
+
description: 'Output raw JSON',
|
|
20
|
+
}),
|
|
21
|
+
limit: Flags.integer({
|
|
22
|
+
default: 20,
|
|
23
|
+
description: 'Number of log entries to display',
|
|
24
|
+
}),
|
|
25
|
+
status: Flags.string({
|
|
26
|
+
description: 'Filter by execution status',
|
|
27
|
+
options: ['success', 'failed'],
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { flags } = await this.parse(HookLogs);
|
|
32
|
+
const isJson = flags.json;
|
|
33
|
+
// Authenticate
|
|
34
|
+
let apiService;
|
|
35
|
+
const spinner = isJson ? null : ora('Checking authentication...').start();
|
|
36
|
+
try {
|
|
37
|
+
apiService = new HyperdriveSigV4Service(flags.domain);
|
|
38
|
+
spinner?.succeed('Authenticated');
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
spinner?.fail('Not authenticated');
|
|
42
|
+
this.error(`${error.message}\n\n` +
|
|
43
|
+
`Please authenticate first with: ${chalk.cyan('hd auth login')}`);
|
|
44
|
+
}
|
|
45
|
+
// Fetch hook logs
|
|
46
|
+
const fetchSpinner = isJson ? null : ora('Fetching hook logs...').start();
|
|
47
|
+
try {
|
|
48
|
+
const response = await apiService.tenantHookLogList({
|
|
49
|
+
limit: flags.limit,
|
|
50
|
+
status: flags.status,
|
|
51
|
+
});
|
|
52
|
+
const items = Array.isArray(response) ? response : (response.items || []);
|
|
53
|
+
if (items.length === 0) {
|
|
54
|
+
fetchSpinner?.succeed('No log entries found');
|
|
55
|
+
if (isJson) {
|
|
56
|
+
this.log(JSON.stringify(items, null, 2));
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
this.log('');
|
|
60
|
+
this.log(chalk.yellow('No hook execution logs found.'));
|
|
61
|
+
this.log('');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
fetchSpinner?.succeed(`Found ${items.length} log entr${items.length === 1 ? 'y' : 'ies'}`);
|
|
65
|
+
if (isJson) {
|
|
66
|
+
this.log(JSON.stringify(items, null, 2));
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
this.log('');
|
|
70
|
+
printTable(items.map(item => ({
|
|
71
|
+
actionType: item.actionType,
|
|
72
|
+
durationMs: item.durationMs,
|
|
73
|
+
hookName: item.hookName,
|
|
74
|
+
status: item.status,
|
|
75
|
+
timestamp: item.timestamp,
|
|
76
|
+
triggerEvent: item.triggerEvent,
|
|
77
|
+
})), {
|
|
78
|
+
timestamp: {
|
|
79
|
+
get: (row) => new Date(row.timestamp).toLocaleString(),
|
|
80
|
+
header: 'Timestamp',
|
|
81
|
+
},
|
|
82
|
+
hookName: { header: 'Hook Name' },
|
|
83
|
+
actionType: { header: 'Action' },
|
|
84
|
+
triggerEvent: { header: 'Event' },
|
|
85
|
+
status: {
|
|
86
|
+
get: (row) => row.status === 'success' ? chalk.green('success') : chalk.red('failed'),
|
|
87
|
+
header: 'Status',
|
|
88
|
+
},
|
|
89
|
+
durationMs: {
|
|
90
|
+
get: (row) => `${row.durationMs}ms`,
|
|
91
|
+
header: 'Duration',
|
|
92
|
+
},
|
|
93
|
+
}, (msg) => this.log(msg));
|
|
94
|
+
this.log('');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
fetchSpinner?.fail('Failed to fetch hook logs');
|
|
98
|
+
this.handleApiError(error);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
handleApiError(error) {
|
|
102
|
+
let errorMessage = error.message;
|
|
103
|
+
if (error.response) {
|
|
104
|
+
const status = error.response.status;
|
|
105
|
+
const data = error.response.data;
|
|
106
|
+
if (status === 401) {
|
|
107
|
+
errorMessage = 'Authentication failed — please run "hd auth login"';
|
|
108
|
+
}
|
|
109
|
+
else if (status === 403) {
|
|
110
|
+
errorMessage = 'Access denied — check your permissions';
|
|
111
|
+
}
|
|
112
|
+
else if (status === 404) {
|
|
113
|
+
errorMessage = 'Not found — verify your tenant domain';
|
|
114
|
+
}
|
|
115
|
+
else if (data?.error) {
|
|
116
|
+
errorMessage = data.error;
|
|
117
|
+
}
|
|
118
|
+
else if (data?.message) {
|
|
119
|
+
errorMessage = data.message;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
this.error(errorMessage);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class HookRemove 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
|
+
}
|