@hyperdrive.bot/cli 1.0.13 → 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 +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 +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
|
@@ -7,6 +7,9 @@ export default class ModuleUpdate extends Command {
|
|
|
7
7
|
'<%= config.bin %> <%= command.id %> --slug="my-module" --runtimeVersion="12"',
|
|
8
8
|
'<%= config.bin %> <%= command.id %> --slug="my-module" --buildCommand="npm run build:prod"',
|
|
9
9
|
'<%= config.bin %> <%= command.id %> --slug="my-module" --name="New Name" --framework="React.js"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --postDeployCommand="npm run migrate" --postDeployFailureMode="warn"',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --runtime-config=\'{"timeout":60,"memory":4096}\'',
|
|
12
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --show',
|
|
10
13
|
];
|
|
11
14
|
static flags = {
|
|
12
15
|
buildCommand: Flags.string({
|
|
@@ -32,9 +35,18 @@ export default class ModuleUpdate extends Command {
|
|
|
32
35
|
defaultBranch: Flags.string({
|
|
33
36
|
description: 'Default git branch to branch from (e.g., main, master)',
|
|
34
37
|
}),
|
|
38
|
+
dependsOn: Flags.string({
|
|
39
|
+
description: 'JSON array of artifact dependencies: [{"artifactKey":"name","downloadTo":"path/"}]',
|
|
40
|
+
}),
|
|
35
41
|
deploymentStrategy: Flags.string({
|
|
36
|
-
description: 'Deployment strategy: "serverless" for
|
|
37
|
-
options: ['serverless'],
|
|
42
|
+
description: 'Deployment strategy: "serverless" for SLS deploy, "docker-build" for Docker-based artifact builds',
|
|
43
|
+
options: ['serverless', 'docker-build'],
|
|
44
|
+
}),
|
|
45
|
+
dockerBuild: Flags.string({
|
|
46
|
+
description: 'Docker-build config JSON: {"image","buildCommand","artifactPaths","artifactKey","workdir","cache"}',
|
|
47
|
+
}),
|
|
48
|
+
'deploy-command': Flags.string({
|
|
49
|
+
description: 'Custom LAUNCH phase deploy command (default: sls deploy --package .serverless --stage <stage> --region <region>). E.g., "npm run deploy:devsquad". BUILD phase uses buildCommand.',
|
|
38
50
|
}),
|
|
39
51
|
domain: Flags.string({
|
|
40
52
|
char: 'd',
|
|
@@ -47,6 +59,17 @@ export default class ModuleUpdate extends Command {
|
|
|
47
59
|
installCommand: Flags.string({
|
|
48
60
|
description: 'Install command',
|
|
49
61
|
}),
|
|
62
|
+
postDeployCommand: Flags.string({
|
|
63
|
+
description: 'Command to run after successful deploy',
|
|
64
|
+
}),
|
|
65
|
+
postDeployFailureMode: Flags.string({
|
|
66
|
+
description: 'Post-deploy command failure behavior: fail (default) or warn',
|
|
67
|
+
options: ['fail', 'warn'],
|
|
68
|
+
}),
|
|
69
|
+
'module-type': Flags.string({
|
|
70
|
+
description: 'Module deployment type (determines API Gateway vs CloudFront vs CDK)',
|
|
71
|
+
options: ['backend', 'frontend', 'cdk'],
|
|
72
|
+
}),
|
|
50
73
|
name: Flags.string({
|
|
51
74
|
description: 'Name of the project',
|
|
52
75
|
}),
|
|
@@ -54,18 +77,28 @@ export default class ModuleUpdate extends Command {
|
|
|
54
77
|
allowNo: true,
|
|
55
78
|
description: 'Enable AI-powered route discovery for per-route Lambda functions',
|
|
56
79
|
}),
|
|
80
|
+
'runtime-mode': Flags.string({
|
|
81
|
+
description: 'Runtime mode: "handler" for native Lambda handlers, "server" for HTTP server wrapping',
|
|
82
|
+
options: ['handler', 'server'],
|
|
83
|
+
}),
|
|
57
84
|
runCommand: Flags.string({
|
|
58
85
|
description: 'Run command',
|
|
59
86
|
}),
|
|
60
87
|
runtime: Flags.string({
|
|
61
88
|
char: 'r',
|
|
62
89
|
description: 'Runtime environment',
|
|
63
|
-
options: ['nodejs', 'python', 'go', 'rust', 'java', 'dotnet', 'ruby'],
|
|
90
|
+
options: ['nodejs', 'python', 'go', 'rust', 'java', 'dotnet', 'ruby', 'static'],
|
|
91
|
+
}),
|
|
92
|
+
'runtime-config': Flags.string({
|
|
93
|
+
description: 'Lambda runtime config JSON: {"timeout":60,"memory":4096}. timeout=1-900s, memory=128-10240MB.',
|
|
64
94
|
}),
|
|
65
95
|
runtimeVersion: Flags.string({
|
|
66
96
|
char: 'v',
|
|
67
97
|
description: 'Runtime version (e.g., 20, 3.12, 1.21)',
|
|
68
98
|
}),
|
|
99
|
+
show: Flags.boolean({
|
|
100
|
+
description: 'Print current module config (including runtimeConfig) without updating',
|
|
101
|
+
}),
|
|
69
102
|
slug: Flags.string({
|
|
70
103
|
char: 's',
|
|
71
104
|
description: 'Module slug to update (required)',
|
|
@@ -77,12 +110,142 @@ export default class ModuleUpdate extends Command {
|
|
|
77
110
|
sourceLocation: Flags.string({
|
|
78
111
|
description: 'Source location of the project',
|
|
79
112
|
}),
|
|
113
|
+
subdomain: Flags.string({
|
|
114
|
+
description: 'Subdomain prefix for custom domain URLs (e.g., "api", "app", "www", "" for apex)',
|
|
115
|
+
}),
|
|
80
116
|
};
|
|
81
117
|
async run() {
|
|
82
118
|
const { flags } = await this.parse(ModuleUpdate);
|
|
83
|
-
const { slug, ...updateFields } = flags;
|
|
119
|
+
const { show, slug, ...updateFields } = flags;
|
|
120
|
+
// --show short-circuit: fetch and print the current module record without updating
|
|
121
|
+
if (show) {
|
|
122
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
123
|
+
try {
|
|
124
|
+
const module = await service.moduleGet({ slug });
|
|
125
|
+
const runtimeConfig = module.runtimeConfig;
|
|
126
|
+
this.log(chalk.green(`✅ Module "${slug}" runtimeConfig:`));
|
|
127
|
+
this.log(JSON.stringify(runtimeConfig ?? null, null, 2));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.error('Error:', error);
|
|
132
|
+
this.error(`An error occurred while fetching module ${slug}`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// Map kebab-case flags to camelCase API fields
|
|
136
|
+
const mappedFields = { ...updateFields };
|
|
137
|
+
if (mappedFields['module-type'] !== undefined) {
|
|
138
|
+
mappedFields.moduleType = mappedFields['module-type'];
|
|
139
|
+
delete mappedFields['module-type'];
|
|
140
|
+
}
|
|
141
|
+
if (mappedFields['deploy-command'] !== undefined) {
|
|
142
|
+
mappedFields.deployCommand = mappedFields['deploy-command'];
|
|
143
|
+
delete mappedFields['deploy-command'];
|
|
144
|
+
}
|
|
145
|
+
if (mappedFields['runtime-mode'] !== undefined) {
|
|
146
|
+
mappedFields.runtimeMode = mappedFields['runtime-mode'];
|
|
147
|
+
delete mappedFields['runtime-mode'];
|
|
148
|
+
}
|
|
149
|
+
if (mappedFields['post-deploy-command'] !== undefined) {
|
|
150
|
+
mappedFields.postDeployCommand = mappedFields['post-deploy-command'];
|
|
151
|
+
delete mappedFields['post-deploy-command'];
|
|
152
|
+
}
|
|
153
|
+
if (mappedFields['post-deploy-failure-mode'] !== undefined) {
|
|
154
|
+
mappedFields.postDeployFailureMode = mappedFields['post-deploy-failure-mode'];
|
|
155
|
+
delete mappedFields['post-deploy-failure-mode'];
|
|
156
|
+
}
|
|
157
|
+
// Parse and validate runtimeConfig JSON (Lambda timeout + memory)
|
|
158
|
+
if (mappedFields['runtime-config'] !== undefined) {
|
|
159
|
+
let parsed;
|
|
160
|
+
try {
|
|
161
|
+
parsed = JSON.parse(mappedFields['runtime-config']);
|
|
162
|
+
}
|
|
163
|
+
catch (error) {
|
|
164
|
+
if (error instanceof SyntaxError) {
|
|
165
|
+
this.error('--runtime-config must be valid JSON: ' + error.message);
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
170
|
+
this.error('--runtime-config must be a JSON object');
|
|
171
|
+
}
|
|
172
|
+
const cfg = parsed;
|
|
173
|
+
const allowedKeys = new Set(['memory', 'timeout']);
|
|
174
|
+
for (const key of Object.keys(cfg)) {
|
|
175
|
+
if (!allowedKeys.has(key)) {
|
|
176
|
+
this.error(`--runtime-config has unknown field "${key}" (allowed: timeout, memory)`);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (cfg.timeout === undefined && cfg.memory === undefined) {
|
|
180
|
+
this.error('--runtime-config must include at least one of "timeout" or "memory"');
|
|
181
|
+
}
|
|
182
|
+
const out = {};
|
|
183
|
+
if (cfg.timeout !== undefined) {
|
|
184
|
+
if (typeof cfg.timeout !== 'number' || !Number.isInteger(cfg.timeout)) {
|
|
185
|
+
this.error('--runtime-config "timeout" must be an integer (seconds)');
|
|
186
|
+
}
|
|
187
|
+
if (cfg.timeout < 1 || cfg.timeout > 900) {
|
|
188
|
+
this.error('--runtime-config "timeout" must be between 1 and 900 seconds (Lambda hard limit)');
|
|
189
|
+
}
|
|
190
|
+
out.timeout = cfg.timeout;
|
|
191
|
+
}
|
|
192
|
+
if (cfg.memory !== undefined) {
|
|
193
|
+
if (typeof cfg.memory !== 'number' || !Number.isInteger(cfg.memory)) {
|
|
194
|
+
this.error('--runtime-config "memory" must be an integer (MB)');
|
|
195
|
+
}
|
|
196
|
+
if (cfg.memory < 128 || cfg.memory > 10_240) {
|
|
197
|
+
this.error('--runtime-config "memory" must be between 128 and 10240 MB (Lambda hard limit)');
|
|
198
|
+
}
|
|
199
|
+
out.memory = cfg.memory;
|
|
200
|
+
}
|
|
201
|
+
mappedFields.runtimeConfig = out;
|
|
202
|
+
delete mappedFields['runtime-config'];
|
|
203
|
+
}
|
|
204
|
+
// Parse and validate dockerBuild JSON
|
|
205
|
+
if (mappedFields.dockerBuild !== undefined) {
|
|
206
|
+
try {
|
|
207
|
+
const parsed = JSON.parse(mappedFields.dockerBuild);
|
|
208
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
209
|
+
this.error('--dockerBuild must be a JSON object');
|
|
210
|
+
}
|
|
211
|
+
if (!parsed.image || !parsed.buildCommand || !parsed.artifactPaths || !parsed.artifactKey) {
|
|
212
|
+
this.error('--dockerBuild must include: image, buildCommand, artifactPaths, artifactKey');
|
|
213
|
+
}
|
|
214
|
+
mappedFields.dockerBuild = parsed;
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
if (error instanceof SyntaxError) {
|
|
218
|
+
this.error('--dockerBuild must be valid JSON: ' + error.message);
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Parse and validate dependsOn JSON
|
|
224
|
+
if (mappedFields.dependsOn !== undefined) {
|
|
225
|
+
try {
|
|
226
|
+
const parsed = JSON.parse(mappedFields.dependsOn);
|
|
227
|
+
if (!Array.isArray(parsed)) {
|
|
228
|
+
this.error('--dependsOn must be a JSON array');
|
|
229
|
+
}
|
|
230
|
+
for (const entry of parsed) {
|
|
231
|
+
if (typeof entry.artifactKey !== 'string' || !entry.artifactKey) {
|
|
232
|
+
this.error('Each dependsOn entry must have a non-empty "artifactKey" string');
|
|
233
|
+
}
|
|
234
|
+
if (typeof entry.downloadTo !== 'string' || !entry.downloadTo) {
|
|
235
|
+
this.error('Each dependsOn entry must have a non-empty "downloadTo" string');
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
mappedFields.dependsOn = parsed;
|
|
239
|
+
}
|
|
240
|
+
catch (error) {
|
|
241
|
+
if (error instanceof SyntaxError) {
|
|
242
|
+
this.error('--dependsOn must be valid JSON: ' + error.message);
|
|
243
|
+
}
|
|
244
|
+
throw error;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
84
247
|
// Filter out undefined values
|
|
85
|
-
const updateData = Object.fromEntries(Object.entries(
|
|
248
|
+
const updateData = Object.fromEntries(Object.entries(mappedFields).filter(([_, value]) => value !== undefined));
|
|
86
249
|
if (Object.keys(updateData).length === 0) {
|
|
87
250
|
this.log(chalk.yellow('No fields to update. Please provide at least one field to update.'));
|
|
88
251
|
return;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class NetworkDiscover extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
account: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
region: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
};
|
|
11
|
+
run(): Promise<void>;
|
|
12
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
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 NetworkDiscover extends Command {
|
|
7
|
+
static description = 'Discover VPCs in a connected AWS account';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> network discover --account 084309335408 --region us-east-1',
|
|
10
|
+
'<%= config.bin %> network discover --account 084309335408 --region us-east-1 --json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
account: Flags.string({
|
|
14
|
+
char: 'a',
|
|
15
|
+
description: 'AWS Account ID to discover VPCs in',
|
|
16
|
+
}),
|
|
17
|
+
domain: Flags.string({
|
|
18
|
+
char: 'd',
|
|
19
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
20
|
+
}),
|
|
21
|
+
json: Flags.boolean({
|
|
22
|
+
description: 'Output raw JSON response (non-interactive)',
|
|
23
|
+
default: false,
|
|
24
|
+
}),
|
|
25
|
+
region: Flags.string({
|
|
26
|
+
char: 'r',
|
|
27
|
+
description: 'AWS region to discover VPCs in',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
async run() {
|
|
31
|
+
const { flags } = await this.parse(NetworkDiscover);
|
|
32
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
33
|
+
// Prompt for missing flags
|
|
34
|
+
let accountId = flags.account;
|
|
35
|
+
let region = flags.region;
|
|
36
|
+
if (!accountId || !region) {
|
|
37
|
+
const inquirer = (await import('inquirer')).default;
|
|
38
|
+
if (!accountId) {
|
|
39
|
+
// Try to fetch registered accounts for selection
|
|
40
|
+
try {
|
|
41
|
+
const accounts = await service.accountList();
|
|
42
|
+
if (accounts.length > 0) {
|
|
43
|
+
const { selectedAccount } = await inquirer.prompt([{
|
|
44
|
+
choices: accounts.map(a => ({
|
|
45
|
+
name: `${a.name || 'Unnamed'} (${a.accountId}) - ${a.defaultRegion}`,
|
|
46
|
+
value: a.accountId,
|
|
47
|
+
})),
|
|
48
|
+
message: chalk.yellow('Select an AWS account:'),
|
|
49
|
+
name: 'selectedAccount',
|
|
50
|
+
type: 'list',
|
|
51
|
+
}]);
|
|
52
|
+
accountId = selectedAccount;
|
|
53
|
+
// Default region from selected account
|
|
54
|
+
if (!region) {
|
|
55
|
+
const selectedAccountObj = accounts.find(a => a.accountId === selectedAccount);
|
|
56
|
+
region = selectedAccountObj?.defaultRegion;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// Fall through to manual input
|
|
62
|
+
}
|
|
63
|
+
if (!accountId) {
|
|
64
|
+
const { inputAccount } = await inquirer.prompt([{
|
|
65
|
+
message: chalk.yellow('Enter the AWS Account ID (12 digits):'),
|
|
66
|
+
name: 'inputAccount',
|
|
67
|
+
validate: (input) => /^\d{12}$/.test(input) || 'AWS Account ID must be exactly 12 digits',
|
|
68
|
+
}]);
|
|
69
|
+
accountId = inputAccount;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (!region) {
|
|
73
|
+
const inquirerModule = (await import('inquirer')).default;
|
|
74
|
+
const { inputRegion } = await inquirerModule.prompt([{
|
|
75
|
+
choices: [
|
|
76
|
+
'us-east-1', 'us-east-2', 'us-west-1', 'us-west-2',
|
|
77
|
+
'sa-east-1', 'eu-west-1', 'eu-west-2', 'eu-central-1',
|
|
78
|
+
'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1',
|
|
79
|
+
],
|
|
80
|
+
default: 'us-east-1',
|
|
81
|
+
message: chalk.yellow('Select the AWS region:'),
|
|
82
|
+
name: 'inputRegion',
|
|
83
|
+
type: 'list',
|
|
84
|
+
}]);
|
|
85
|
+
region = inputRegion;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const spinner = ora(`Discovering VPCs in ${accountId}/${region}...`).start();
|
|
89
|
+
try {
|
|
90
|
+
const result = await service.networkDiscover({
|
|
91
|
+
accountId: accountId,
|
|
92
|
+
region: region,
|
|
93
|
+
});
|
|
94
|
+
spinner.stop();
|
|
95
|
+
// JSON mode — output raw response and exit
|
|
96
|
+
if (flags.json) {
|
|
97
|
+
this.log(JSON.stringify(result, null, 2));
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (result.vpcs.length === 0) {
|
|
101
|
+
this.log(chalk.yellow(`\nNo VPCs found in ${accountId}/${region}.`));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.log(chalk.blue(`\nDiscovered ${result.vpcs.length} VPC(s) in ${accountId}/${region}:\n`));
|
|
105
|
+
printTable(result.vpcs, {
|
|
106
|
+
vpcId: {
|
|
107
|
+
header: 'VPC ID',
|
|
108
|
+
minWidth: 25,
|
|
109
|
+
get: (row) => chalk.cyan(row.vpcId),
|
|
110
|
+
},
|
|
111
|
+
name: {
|
|
112
|
+
header: 'Name',
|
|
113
|
+
minWidth: 20,
|
|
114
|
+
get: (row) => row.name || chalk.gray('(unnamed)'),
|
|
115
|
+
},
|
|
116
|
+
cidrBlock: {
|
|
117
|
+
header: 'CIDR',
|
|
118
|
+
minWidth: 18,
|
|
119
|
+
},
|
|
120
|
+
subnets: {
|
|
121
|
+
header: 'Subnets (Priv/Pub)',
|
|
122
|
+
minWidth: 20,
|
|
123
|
+
get: (row) => `${row.privateSubnetIds.length} / ${row.publicSubnetIds.length}`,
|
|
124
|
+
},
|
|
125
|
+
sgs: {
|
|
126
|
+
header: 'Security Groups',
|
|
127
|
+
minWidth: 16,
|
|
128
|
+
get: (row) => String(row.securityGroups.length),
|
|
129
|
+
},
|
|
130
|
+
registered: {
|
|
131
|
+
header: 'Registered?',
|
|
132
|
+
get: (row) => row.alreadyRegistered
|
|
133
|
+
? chalk.green(`Yes (${row.existingNetworkSlug})`)
|
|
134
|
+
: chalk.gray('No'),
|
|
135
|
+
},
|
|
136
|
+
}, (msg) => this.log(msg));
|
|
137
|
+
// Interactive mode — select VPCs to register
|
|
138
|
+
const unregisteredVpcs = result.vpcs.filter(v => !v.alreadyRegistered);
|
|
139
|
+
if (unregisteredVpcs.length === 0) {
|
|
140
|
+
this.log(chalk.green('\nAll discovered VPCs are already registered.'));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const inquirer = (await import('inquirer')).default;
|
|
144
|
+
const { selectedVpcs } = await inquirer.prompt([{
|
|
145
|
+
choices: unregisteredVpcs.map(v => ({
|
|
146
|
+
name: `${v.vpcId} - ${v.name || '(unnamed)'} (${v.cidrBlock})`,
|
|
147
|
+
value: v.vpcId,
|
|
148
|
+
})),
|
|
149
|
+
message: chalk.yellow('\nSelect VPCs to register as networks:'),
|
|
150
|
+
name: 'selectedVpcs',
|
|
151
|
+
type: 'checkbox',
|
|
152
|
+
}]);
|
|
153
|
+
if (selectedVpcs.length === 0) {
|
|
154
|
+
this.log(chalk.gray('\nNo VPCs selected. Done.'));
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Register each selected VPC
|
|
158
|
+
for (const vpcId of selectedVpcs) {
|
|
159
|
+
const vpc = unregisteredVpcs.find(v => v.vpcId === vpcId);
|
|
160
|
+
// Auto-generate slug from name tag or VPC ID suffix
|
|
161
|
+
const slugBase = vpc.name
|
|
162
|
+
? vpc.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
|
163
|
+
: `vpc-${vpcId.slice(-8)}`;
|
|
164
|
+
const slug = `${slugBase}-${region}`;
|
|
165
|
+
const { confirmedSlug } = await inquirer.prompt([{
|
|
166
|
+
default: slug,
|
|
167
|
+
message: chalk.yellow(`Slug for ${vpcId}:`),
|
|
168
|
+
name: 'confirmedSlug',
|
|
169
|
+
validate: (input) => /^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/.test(input) || 'slug must be lowercase alphanumeric with hyphens',
|
|
170
|
+
}]);
|
|
171
|
+
const registerSpinner = ora(`Registering ${confirmedSlug}...`).start();
|
|
172
|
+
try {
|
|
173
|
+
await service.networkRegister({
|
|
174
|
+
slug: confirmedSlug,
|
|
175
|
+
accountId: accountId,
|
|
176
|
+
region: region,
|
|
177
|
+
vpcId,
|
|
178
|
+
privateSubnetIds: vpc.privateSubnetIds,
|
|
179
|
+
publicSubnetIds: vpc.publicSubnetIds,
|
|
180
|
+
source: 'discovered',
|
|
181
|
+
});
|
|
182
|
+
registerSpinner.succeed(chalk.green(`Registered: ${confirmedSlug}`));
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
const axiosErr = error;
|
|
186
|
+
const errMsg = axiosErr.response?.data?.message ?? axiosErr.message ?? 'Unknown error';
|
|
187
|
+
registerSpinner.fail(chalk.red(`Failed to register ${confirmedSlug}: ${errMsg}`));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
this.log(chalk.green('\nDone.'));
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
spinner.stop();
|
|
194
|
+
const axiosError = error;
|
|
195
|
+
const statusCode = axiosError.response?.status;
|
|
196
|
+
const errorMessage = axiosError.response?.data?.message ?? axiosError.message ?? 'Unknown error';
|
|
197
|
+
if (statusCode === 403) {
|
|
198
|
+
this.log(chalk.red(`\n❌ Access denied: ${errorMessage}`));
|
|
199
|
+
this.log(chalk.gray('Ensure HyperdriveDiscoveryRole exists in the target account and allows this account to assume it.'));
|
|
200
|
+
}
|
|
201
|
+
else if (statusCode === 502) {
|
|
202
|
+
this.log(chalk.red(`\n❌ Discovery failed: ${errorMessage}`));
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
this.log(chalk.red(`\n❌ Error: ${errorMessage}`));
|
|
206
|
+
}
|
|
207
|
+
this.exit(1);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class NetworkGet extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
slug: 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
|
+
};
|
|
12
|
+
run(): Promise<void>;
|
|
13
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
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 } from '../../utils/table.js';
|
|
6
|
+
export default class NetworkGet extends Command {
|
|
7
|
+
static description = 'Get details of a registered network';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> network get prod-us-east-1',
|
|
10
|
+
'<%= config.bin %> network get prod-us-east-1 --json',
|
|
11
|
+
];
|
|
12
|
+
static args = {
|
|
13
|
+
slug: Args.string({
|
|
14
|
+
description: 'Network slug',
|
|
15
|
+
required: true,
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
static flags = {
|
|
19
|
+
domain: Flags.string({
|
|
20
|
+
char: 'd',
|
|
21
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
22
|
+
}),
|
|
23
|
+
json: Flags.boolean({
|
|
24
|
+
description: 'Output raw JSON response',
|
|
25
|
+
default: false,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
async run() {
|
|
29
|
+
const { args, flags } = await this.parse(NetworkGet);
|
|
30
|
+
const isJson = flags.json;
|
|
31
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
32
|
+
const spinner = isJson ? null : ora(`Fetching network "${args.slug}"...`).start();
|
|
33
|
+
try {
|
|
34
|
+
const network = await service.networkGet(args.slug);
|
|
35
|
+
spinner?.succeed('Network found');
|
|
36
|
+
if (isJson) {
|
|
37
|
+
this.log(JSON.stringify(network, null, 2));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
printHeader(`Network: ${network.slug}`, (msg) => this.log(msg));
|
|
41
|
+
this.log(` ${chalk.bold('Account ID:')} ${network.accountId}`);
|
|
42
|
+
this.log(` ${chalk.bold('Region:')} ${network.region}`);
|
|
43
|
+
this.log(` ${chalk.bold('VPC ID:')} ${chalk.cyan(network.vpcId)}`);
|
|
44
|
+
this.log(` ${chalk.bold('Status:')} ${network.status === 'active' ? chalk.green(network.status) : chalk.yellow(network.status)}`);
|
|
45
|
+
this.log(` ${chalk.bold('Source:')} ${network.source}`);
|
|
46
|
+
this.log(` ${chalk.bold('Created:')} ${new Date(network.createdAt).toLocaleString()}`);
|
|
47
|
+
this.log(` ${chalk.bold('Updated:')} ${new Date(network.updatedAt).toLocaleString()}`);
|
|
48
|
+
this.log(`\n ${chalk.bold('Private Subnets:')}`);
|
|
49
|
+
if (network.privateSubnetIds.length > 0) {
|
|
50
|
+
for (const subnet of network.privateSubnetIds) {
|
|
51
|
+
this.log(` - ${subnet}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
this.log(chalk.gray(' (none)'));
|
|
56
|
+
}
|
|
57
|
+
this.log(`\n ${chalk.bold('Public Subnets:')}`);
|
|
58
|
+
if (network.publicSubnetIds.length > 0) {
|
|
59
|
+
for (const subnet of network.publicSubnetIds) {
|
|
60
|
+
this.log(` - ${subnet}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.log(chalk.gray(' (none)'));
|
|
65
|
+
}
|
|
66
|
+
this.log(`\n ${chalk.bold('Security Groups:')}`);
|
|
67
|
+
const sgEntries = Object.entries(network.securityGroups || {});
|
|
68
|
+
if (sgEntries.length > 0) {
|
|
69
|
+
for (const [purpose, groupId] of sgEntries) {
|
|
70
|
+
this.log(` ${chalk.bold(purpose)}: ${groupId}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
this.log(chalk.gray(' (none)'));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
spinner?.fail('Failed');
|
|
79
|
+
const axiosError = error;
|
|
80
|
+
const status = axiosError.response?.status;
|
|
81
|
+
if (status === 404) {
|
|
82
|
+
this.log(chalk.red(`\n❌ Network not found: ${args.slug}`));
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
this.log(chalk.red(`\n❌ ${axiosError.response?.data?.message ?? axiosError.message}`));
|
|
86
|
+
}
|
|
87
|
+
this.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -1,17 +1,10 @@
|
|
|
1
1
|
import { Command } from '@oclif/core';
|
|
2
|
-
export default class
|
|
2
|
+
export default class NetworkList extends Command {
|
|
3
3
|
static description: string;
|
|
4
4
|
static examples: string[];
|
|
5
5
|
static flags: {
|
|
6
6
|
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
json: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
7
8
|
};
|
|
8
9
|
run(): Promise<void>;
|
|
9
|
-
/**
|
|
10
|
-
* Remove credentials for a specific domain
|
|
11
|
-
*/
|
|
12
|
-
private logoutDomain;
|
|
13
|
-
/**
|
|
14
|
-
* Remove default domain credentials and all stored config
|
|
15
|
-
*/
|
|
16
|
-
private logoutAll;
|
|
17
10
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
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 NetworkList extends Command {
|
|
7
|
+
static description = 'List all registered networks';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> network list',
|
|
10
|
+
'<%= config.bin %> network list --json',
|
|
11
|
+
];
|
|
12
|
+
static flags = {
|
|
13
|
+
domain: Flags.string({
|
|
14
|
+
char: 'd',
|
|
15
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
16
|
+
}),
|
|
17
|
+
json: Flags.boolean({
|
|
18
|
+
description: 'Output raw JSON response',
|
|
19
|
+
default: false,
|
|
20
|
+
}),
|
|
21
|
+
};
|
|
22
|
+
async run() {
|
|
23
|
+
const { flags } = await this.parse(NetworkList);
|
|
24
|
+
const isJson = flags.json;
|
|
25
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
26
|
+
const spinner = isJson ? null : ora('Fetching networks...').start();
|
|
27
|
+
try {
|
|
28
|
+
const networks = await service.networkList();
|
|
29
|
+
spinner?.stop();
|
|
30
|
+
if (isJson) {
|
|
31
|
+
this.log(JSON.stringify(networks, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!networks || networks.length === 0) {
|
|
35
|
+
this.log(chalk.yellow('\nNo networks found.'));
|
|
36
|
+
this.log(chalk.gray('Use "hd network discover" or "hd network register" to add networks.'));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
this.log(chalk.green(`\n${networks.length} network(s) found:\n`));
|
|
40
|
+
printTable(networks, {
|
|
41
|
+
slug: {
|
|
42
|
+
header: 'Slug',
|
|
43
|
+
minWidth: 25,
|
|
44
|
+
get: (row) => chalk.cyan(row.slug),
|
|
45
|
+
},
|
|
46
|
+
accountId: {
|
|
47
|
+
header: 'Account ID',
|
|
48
|
+
minWidth: 15,
|
|
49
|
+
},
|
|
50
|
+
region: {
|
|
51
|
+
header: 'Region',
|
|
52
|
+
minWidth: 15,
|
|
53
|
+
},
|
|
54
|
+
vpcId: {
|
|
55
|
+
header: 'VPC ID',
|
|
56
|
+
minWidth: 25,
|
|
57
|
+
},
|
|
58
|
+
status: {
|
|
59
|
+
header: 'Status',
|
|
60
|
+
get: (row) => row.status === 'active' ? chalk.green(row.status) : chalk.yellow(row.status),
|
|
61
|
+
},
|
|
62
|
+
}, (msg) => this.log(msg));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
spinner?.fail('Failed to fetch networks');
|
|
66
|
+
const axiosError = error;
|
|
67
|
+
this.log(chalk.red(`\n❌ ${axiosError.response?.data?.message ?? axiosError.message}`));
|
|
68
|
+
this.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class NetworkRegister extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
'account-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
'private-subnets': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
'public-subnets': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
region: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
'security-groups': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
slug: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
13
|
+
'vpc-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|