@hyperdrive.bot/cli 1.0.2
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 +1598 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +3 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +5 -0
- package/dist/commands/account/add.d.ts +16 -0
- package/dist/commands/account/add.js +185 -0
- package/dist/commands/account/list.d.ts +6 -0
- package/dist/commands/account/list.js +37 -0
- package/dist/commands/account/remove.d.ts +11 -0
- package/dist/commands/account/remove.js +57 -0
- package/dist/commands/auth/login.d.ts +16 -0
- package/dist/commands/auth/login.js +178 -0
- package/dist/commands/auth/logout.d.ts +6 -0
- package/dist/commands/auth/logout.js +39 -0
- package/dist/commands/auth/refresh.d.ts +6 -0
- package/dist/commands/auth/refresh.js +66 -0
- package/dist/commands/auth/status.d.ts +6 -0
- package/dist/commands/auth/status.js +63 -0
- package/dist/commands/ci/account/create.d.ts +16 -0
- package/dist/commands/ci/account/create.js +158 -0
- package/dist/commands/ci/account/delete.d.ts +14 -0
- package/dist/commands/ci/account/delete.js +88 -0
- package/dist/commands/ci/account/list.d.ts +10 -0
- package/dist/commands/ci/account/list.js +65 -0
- package/dist/commands/config/get.d.ts +9 -0
- package/dist/commands/config/get.js +37 -0
- package/dist/commands/config/set.d.ts +10 -0
- package/dist/commands/config/set.js +48 -0
- package/dist/commands/config/show.d.ts +6 -0
- package/dist/commands/config/show.js +10 -0
- package/dist/commands/deployment/create.d.ts +30 -0
- package/dist/commands/deployment/create.js +188 -0
- package/dist/commands/deployment/get.d.ts +13 -0
- package/dist/commands/deployment/get.js +101 -0
- package/dist/commands/deployment/launch.d.ts +15 -0
- package/dist/commands/deployment/launch.js +105 -0
- package/dist/commands/deployment/list.d.ts +11 -0
- package/dist/commands/deployment/list.js +91 -0
- package/dist/commands/domain/current.d.ts +6 -0
- package/dist/commands/domain/current.js +18 -0
- package/dist/commands/domain/list.d.ts +6 -0
- package/dist/commands/domain/list.js +42 -0
- package/dist/commands/domain/switch.d.ts +9 -0
- package/dist/commands/domain/switch.js +40 -0
- package/dist/commands/example.d.ts +13 -0
- package/dist/commands/example.js +24 -0
- package/dist/commands/git/connect.d.ts +10 -0
- package/dist/commands/git/connect.js +56 -0
- package/dist/commands/git/disconnect.d.ts +11 -0
- package/dist/commands/git/disconnect.js +93 -0
- package/dist/commands/git/list.d.ts +10 -0
- package/dist/commands/git/list.js +53 -0
- package/dist/commands/git/sync.d.ts +18 -0
- package/dist/commands/git/sync.js +235 -0
- package/dist/commands/init.d.ts +188 -0
- package/dist/commands/init.js +817 -0
- package/dist/commands/jira/connect.d.ts +9 -0
- package/dist/commands/jira/connect.js +141 -0
- package/dist/commands/jira/status.d.ts +9 -0
- package/dist/commands/jira/status.js +118 -0
- package/dist/commands/module/analyze.d.ts +29 -0
- package/dist/commands/module/analyze.js +201 -0
- package/dist/commands/module/create.d.ts +42 -0
- package/dist/commands/module/create.js +498 -0
- package/dist/commands/module/destroy.d.ts +11 -0
- package/dist/commands/module/destroy.js +77 -0
- package/dist/commands/module/get.d.ts +10 -0
- package/dist/commands/module/get.js +43 -0
- package/dist/commands/module/link.d.ts +15 -0
- package/dist/commands/module/link.js +175 -0
- package/dist/commands/module/list.d.ts +9 -0
- package/dist/commands/module/list.js +51 -0
- package/dist/commands/module/reanalyze.d.ts +30 -0
- package/dist/commands/module/reanalyze.js +206 -0
- package/dist/commands/module/update.d.ts +27 -0
- package/dist/commands/module/update.js +102 -0
- package/dist/commands/parameter/add.d.ts +15 -0
- package/dist/commands/parameter/add.js +99 -0
- package/dist/commands/parameter/backfill.d.ts +12 -0
- package/dist/commands/parameter/backfill.js +113 -0
- package/dist/commands/parameter/clear.d.ts +14 -0
- package/dist/commands/parameter/clear.js +95 -0
- package/dist/commands/parameter/list.d.ts +14 -0
- package/dist/commands/parameter/list.js +92 -0
- package/dist/commands/parameter/pull.d.ts +14 -0
- package/dist/commands/parameter/pull.js +124 -0
- package/dist/commands/parameter/remove.d.ts +15 -0
- package/dist/commands/parameter/remove.js +90 -0
- package/dist/commands/parameter/sync.d.ts +14 -0
- package/dist/commands/parameter/sync.js +153 -0
- package/dist/commands/parameter/update.d.ts +15 -0
- package/dist/commands/parameter/update.js +100 -0
- package/dist/commands/stage/create.d.ts +28 -0
- package/dist/commands/stage/create.js +312 -0
- package/dist/commands/stage/list.d.ts +9 -0
- package/dist/commands/stage/list.js +63 -0
- package/dist/commands/test-api.d.ts +9 -0
- package/dist/commands/test-api.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/services/auth-service.d.ts +84 -0
- package/dist/services/auth-service.js +240 -0
- package/dist/services/git.d.ts +46 -0
- package/dist/services/git.js +409 -0
- package/dist/services/hyperdrive-sigv4.d.ts +449 -0
- package/dist/services/hyperdrive-sigv4.js +375 -0
- package/dist/services/hyperdrive.d.ts +87 -0
- package/dist/services/hyperdrive.js +108 -0
- package/dist/services/log-tailer.d.ts +95 -0
- package/dist/services/log-tailer.js +242 -0
- package/dist/services/tenant-service.d.ts +106 -0
- package/dist/services/tenant-service.js +332 -0
- package/dist/utils/account-flow.d.ts +74 -0
- package/dist/utils/account-flow.js +228 -0
- package/dist/utils/auth-flow.d.ts +146 -0
- package/dist/utils/auth-flow.js +477 -0
- package/dist/utils/git-flow.d.ts +72 -0
- package/dist/utils/git-flow.js +232 -0
- package/dist/utils/jira-flow.d.ts +71 -0
- package/dist/utils/jira-flow.js +120 -0
- package/dist/utils/summary-display.d.ts +59 -0
- package/dist/utils/summary-display.js +140 -0
- package/dist/utils/validation.d.ts +15 -0
- package/dist/utils/validation.js +32 -0
- package/oclif.manifest.json +2819 -0
- package/package.json +112 -0
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
export default class ModuleLink extends Command {
|
|
6
|
+
static description = 'Links two modules using specified environment variables as connectors';
|
|
7
|
+
static examples = [
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --originSlug="vixting-integration" --targetSlug="vixting-api" --parameter="VLOW_API_ENDPOINT_PREFIX"',
|
|
9
|
+
];
|
|
10
|
+
static flags = {
|
|
11
|
+
domain: Flags.string({
|
|
12
|
+
char: 'd',
|
|
13
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
14
|
+
}),
|
|
15
|
+
originSlug: Flags.string({
|
|
16
|
+
description: 'Slug of the origin module (the one that depends on another)',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
parameter: Flags.string({
|
|
20
|
+
description: 'Environment variable to link (e.g., API_URL)',
|
|
21
|
+
multiple: true,
|
|
22
|
+
required: true,
|
|
23
|
+
}),
|
|
24
|
+
targetSlug: Flags.string({
|
|
25
|
+
description: 'Slug of the target module (the dependency)',
|
|
26
|
+
required: true,
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
async confirmDeploymentBehavior() {
|
|
30
|
+
const confirmation = await inquirer.prompt([
|
|
31
|
+
{
|
|
32
|
+
default: true,
|
|
33
|
+
message: chalk.blue('In every deployment, we will attempt to find a backend instance in the same stage as your application is being deployed to. If no such backend instance is found, we will connect your frontend to the backend instance deployed in the default stage. Do you want to proceed?'),
|
|
34
|
+
name: 'confirmDeployment',
|
|
35
|
+
type: 'confirm',
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
return confirmation.confirmDeployment;
|
|
39
|
+
}
|
|
40
|
+
async prompt() {
|
|
41
|
+
await inquirer.prompt([
|
|
42
|
+
{
|
|
43
|
+
message: chalk.green('Enter the origin module slug:'),
|
|
44
|
+
name: 'originSlug',
|
|
45
|
+
validate: input => Boolean(input) || 'Origin module slug is required.',
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
await inquirer.prompt([
|
|
49
|
+
{
|
|
50
|
+
choices: ['front-end', 'back-end'],
|
|
51
|
+
message: chalk.green('What is the type of the module?'),
|
|
52
|
+
name: 'projectType',
|
|
53
|
+
type: 'list',
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
await this.promptForEnvironmentVariables();
|
|
57
|
+
await inquirer.prompt([
|
|
58
|
+
{
|
|
59
|
+
message: chalk.green('Enter the destination module slug:'),
|
|
60
|
+
name: 'targetSlug',
|
|
61
|
+
validate: input => Boolean(input) || 'Destination module slug is required.',
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
const proceed = await this.confirmDeploymentBehavior();
|
|
65
|
+
if (!proceed) {
|
|
66
|
+
this.log(chalk.red('Deployment cancelled by user.'));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async promptForEnvironmentVariables() {
|
|
70
|
+
const envVariables = [];
|
|
71
|
+
const collectVariable = async () => {
|
|
72
|
+
const response = await inquirer.prompt([
|
|
73
|
+
{
|
|
74
|
+
message: chalk.yellow('Enter the environment variable used to connect your front-end to your back-end:'),
|
|
75
|
+
name: 'envVariable',
|
|
76
|
+
validate: input => Boolean(input) || 'Environment variable is required.',
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
default: false,
|
|
80
|
+
message: chalk.yellow('Do you want to add another environment variable?'),
|
|
81
|
+
name: 'addAnother',
|
|
82
|
+
type: 'confirm',
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
envVariables.push(response.envVariable);
|
|
86
|
+
if (response.addAnother) {
|
|
87
|
+
return collectVariable();
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
await collectVariable();
|
|
91
|
+
return envVariables;
|
|
92
|
+
}
|
|
93
|
+
async run() {
|
|
94
|
+
const { flags } = await this.parse(ModuleLink);
|
|
95
|
+
// Validation: Ensure origin and target are different
|
|
96
|
+
if (flags.originSlug === flags.targetSlug) {
|
|
97
|
+
this.log(chalk.red('❌ Error: Cannot link a module to itself'));
|
|
98
|
+
this.log(chalk.gray(' originSlug and targetSlug must be different'));
|
|
99
|
+
this.exit(1);
|
|
100
|
+
}
|
|
101
|
+
// Validation: Ensure at least one parameter
|
|
102
|
+
if (!flags.parameter || flags.parameter.length === 0) {
|
|
103
|
+
this.log(chalk.red('❌ Error: At least one parameter is required'));
|
|
104
|
+
this.log(chalk.gray(' Use --parameter=PARAM_NAME to specify parameters'));
|
|
105
|
+
this.exit(1);
|
|
106
|
+
}
|
|
107
|
+
// Validation: Check for duplicate parameters
|
|
108
|
+
const uniqueParams = new Set(flags.parameter);
|
|
109
|
+
if (uniqueParams.size !== flags.parameter.length) {
|
|
110
|
+
this.log(chalk.yellow('⚠️ Warning: Duplicate parameters detected, removing duplicates'));
|
|
111
|
+
}
|
|
112
|
+
this.log(chalk.green(`🔗 Linking ${flags.originSlug} → ${flags.targetSlug}...`));
|
|
113
|
+
this.log(chalk.gray(` Parameters: ${[...uniqueParams].join(', ')}`));
|
|
114
|
+
try {
|
|
115
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
116
|
+
// Convert array of parameter names to object format
|
|
117
|
+
// Each parameter will receive the target module's deployment URL at deploy time
|
|
118
|
+
const parametersObject = {};
|
|
119
|
+
for (const param of uniqueParams) {
|
|
120
|
+
parametersObject[param] = true;
|
|
121
|
+
}
|
|
122
|
+
const result = await service.moduleLink({
|
|
123
|
+
originSlug: flags.originSlug,
|
|
124
|
+
parameters: parametersObject,
|
|
125
|
+
targetSlug: flags.targetSlug,
|
|
126
|
+
});
|
|
127
|
+
this.log('');
|
|
128
|
+
this.log(chalk.blue('✅ Module link successful!'));
|
|
129
|
+
this.log('');
|
|
130
|
+
this.log(chalk.bold('What happens during deployment:'));
|
|
131
|
+
this.log(chalk.gray(` 1. When you deploy ${chalk.cyan(flags.originSlug)}, Hyperdrive will:`));
|
|
132
|
+
this.log(chalk.gray(` - Look for ${chalk.cyan(flags.targetSlug)} in the same stage`));
|
|
133
|
+
this.log(chalk.gray(` - If not found, fall back to default stage`));
|
|
134
|
+
this.log(chalk.gray(` - Inject the deployment URL into these parameters:`));
|
|
135
|
+
for (const param of uniqueParams) {
|
|
136
|
+
this.log(chalk.gray(` • ${chalk.yellow(param)}`));
|
|
137
|
+
}
|
|
138
|
+
this.log('');
|
|
139
|
+
this.log(chalk.gray('Example: If you deploy to "prod" stage:'));
|
|
140
|
+
this.log(chalk.gray(` - First deploy ${chalk.cyan(flags.targetSlug)} to prod → gets URL https://api.example.com`));
|
|
141
|
+
this.log(chalk.gray(` - Then deploy ${chalk.cyan(flags.originSlug)} to prod → receives ${chalk.yellow(flags.parameter[0])}=https://api.example.com`));
|
|
142
|
+
this.log('');
|
|
143
|
+
this.log(chalk.gray('Link details:'));
|
|
144
|
+
this.log(JSON.stringify(result, null, 2));
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
this.log('');
|
|
148
|
+
this.log(chalk.red('❌ Error linking modules'));
|
|
149
|
+
this.log('');
|
|
150
|
+
// Parse error response for better messages
|
|
151
|
+
if (error.response?.data?.message) {
|
|
152
|
+
this.log(chalk.red('Error: ' + error.response.data.message));
|
|
153
|
+
}
|
|
154
|
+
else if (error.message) {
|
|
155
|
+
this.log(chalk.red('Error: ' + error.message));
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
this.log(chalk.red('Error: ' + error.toString()));
|
|
159
|
+
}
|
|
160
|
+
// Provide helpful hints based on common errors
|
|
161
|
+
if (error.response?.status === 404) {
|
|
162
|
+
this.log('');
|
|
163
|
+
this.log(chalk.yellow('💡 Tip: Make sure both modules exist'));
|
|
164
|
+
this.log(chalk.gray(` Run: hd module list`));
|
|
165
|
+
}
|
|
166
|
+
else if (error.response?.status === 400) {
|
|
167
|
+
this.log('');
|
|
168
|
+
this.log(chalk.yellow('💡 Tip: Check your command syntax'));
|
|
169
|
+
this.log(chalk.gray(` Example: hd module link --originSlug=my-app --targetSlug=my-api --parameter=API_URL`));
|
|
170
|
+
}
|
|
171
|
+
this.log('');
|
|
172
|
+
this.exit(1);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ModuleList extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
};
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Command, Flags, ux } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
4
|
+
export default class ModuleList extends Command {
|
|
5
|
+
static description = 'List all modules/projects';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %>',
|
|
8
|
+
];
|
|
9
|
+
static flags = {
|
|
10
|
+
domain: Flags.string({
|
|
11
|
+
char: 'd',
|
|
12
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
async run() {
|
|
16
|
+
const { flags } = await this.parse(ModuleList);
|
|
17
|
+
try {
|
|
18
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
19
|
+
this.log(chalk.blue('🔍 Fetching modules...'));
|
|
20
|
+
const modules = await service.moduleList();
|
|
21
|
+
if (!modules || modules.length === 0) {
|
|
22
|
+
this.log(chalk.yellow('⚠️ No modules found'));
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
this.log(chalk.green(`✅ Found ${modules.length} module(s)\n`));
|
|
26
|
+
// Display modules in a table
|
|
27
|
+
ux.table(modules, {
|
|
28
|
+
createdAt: {
|
|
29
|
+
get: (row) => new Date(row.createdAt).toLocaleDateString(),
|
|
30
|
+
header: 'Created',
|
|
31
|
+
},
|
|
32
|
+
framework: {
|
|
33
|
+
header: 'Framework',
|
|
34
|
+
},
|
|
35
|
+
name: {
|
|
36
|
+
header: 'Name',
|
|
37
|
+
minWidth: 20,
|
|
38
|
+
},
|
|
39
|
+
slug: {
|
|
40
|
+
get: (row) => chalk.cyan(row.slug),
|
|
41
|
+
header: 'Slug',
|
|
42
|
+
minWidth: 15,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
console.error('Error:', error);
|
|
48
|
+
this.error('An error occurred while listing modules');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class DockerfileReanalyze extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
prompt: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
save: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
slug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
verbose: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
wait: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Build CloudWatch console URL for log viewing
|
|
16
|
+
*/
|
|
17
|
+
private buildConsoleUrl;
|
|
18
|
+
/**
|
|
19
|
+
* Display analysis results
|
|
20
|
+
*/
|
|
21
|
+
private displayResults;
|
|
22
|
+
/**
|
|
23
|
+
* Poll for Dockerfile (fallback when streaming not available)
|
|
24
|
+
*/
|
|
25
|
+
private pollForDockerfile;
|
|
26
|
+
/**
|
|
27
|
+
* Stream analysis logs using CloudWatch
|
|
28
|
+
*/
|
|
29
|
+
private streamAnalysisLogs;
|
|
30
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
5
|
+
import { CloudWatchLogTailer } from '../../services/log-tailer.js';
|
|
6
|
+
export default class DockerfileReanalyze extends Command {
|
|
7
|
+
static description = 'Reanalyze a module\'s Dockerfile with custom instructions';
|
|
8
|
+
static examples = [
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --prompt="Use Node.js 18 instead of 20"',
|
|
10
|
+
'<%= config.bin %> <%= command.id %> --prompt="Fix the build - missing Python dependency"',
|
|
11
|
+
'<%= config.bin %> <%= command.id %> -s my-module -p "Exclude source maps from S3 upload" --verbose',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
domain: Flags.string({
|
|
15
|
+
char: 'd',
|
|
16
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
17
|
+
}),
|
|
18
|
+
prompt: Flags.string({
|
|
19
|
+
char: 'p',
|
|
20
|
+
description: 'Custom instructions for Dockerfile regeneration',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
save: Flags.boolean({
|
|
24
|
+
default: false,
|
|
25
|
+
description: 'Save generated Dockerfile to current directory',
|
|
26
|
+
}),
|
|
27
|
+
slug: Flags.string({
|
|
28
|
+
char: 's',
|
|
29
|
+
default() {
|
|
30
|
+
if (existsSync('.hyperdrive.json')) {
|
|
31
|
+
const hyperdriveConfig = JSON.parse(readFileSync('.hyperdrive.json', 'utf8'));
|
|
32
|
+
return hyperdriveConfig.slug;
|
|
33
|
+
}
|
|
34
|
+
return '';
|
|
35
|
+
},
|
|
36
|
+
description: 'Module slug to reanalyze',
|
|
37
|
+
required: true,
|
|
38
|
+
}),
|
|
39
|
+
verbose: Flags.boolean({
|
|
40
|
+
char: 'v',
|
|
41
|
+
default: false,
|
|
42
|
+
description: 'Show detailed analysis progress and real-time logs',
|
|
43
|
+
}),
|
|
44
|
+
wait: Flags.boolean({
|
|
45
|
+
char: 'w',
|
|
46
|
+
default: true,
|
|
47
|
+
description: 'Wait for reanalysis to complete and show results',
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
const { flags } = await this.parse(DockerfileReanalyze);
|
|
52
|
+
try {
|
|
53
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
54
|
+
this.log(chalk.blue(`🔄 Reanalyzing module ${chalk.cyan(flags.slug)}...`));
|
|
55
|
+
this.log(chalk.gray(`Custom instructions: ${chalk.italic(flags.prompt)}\n`));
|
|
56
|
+
if (flags.verbose) {
|
|
57
|
+
this.log(chalk.gray('📝 Verbose mode enabled - streaming real-time logs\n'));
|
|
58
|
+
}
|
|
59
|
+
const result = await service.moduleReanalyze(flags.slug, flags.prompt);
|
|
60
|
+
this.log(chalk.green(`✅ Reanalysis queued successfully!`));
|
|
61
|
+
this.log(chalk.gray(` Job ID: ${result.jobId}`));
|
|
62
|
+
this.log(chalk.gray(` Status: ${result.status}`));
|
|
63
|
+
if (flags.verbose && result.logging) {
|
|
64
|
+
this.log(chalk.gray(` Project ID: ${result.projectId}`));
|
|
65
|
+
}
|
|
66
|
+
if (!flags.wait) {
|
|
67
|
+
this.log(chalk.yellow('\nUse --wait to poll for results, or check later with:'));
|
|
68
|
+
this.log(chalk.cyan(` hyperdrive module dockerfile --slug ${flags.slug}`));
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
// If verbose and API returned logging config, stream logs
|
|
72
|
+
if (flags.verbose && result.logging) {
|
|
73
|
+
await this.streamAnalysisLogs(result.logging, flags, service);
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// Fallback to polling
|
|
77
|
+
if (flags.verbose && !result.logging) {
|
|
78
|
+
this.log(chalk.yellow('⚠️ Log streaming not available (API limitation), falling back to polling'));
|
|
79
|
+
}
|
|
80
|
+
this.log(chalk.blue('\n⏳ Waiting for reanalysis to complete...'));
|
|
81
|
+
this.log(chalk.gray(' This typically takes 2-5 minutes.\n'));
|
|
82
|
+
const dockerfile = await this.pollForDockerfile(service, flags.slug);
|
|
83
|
+
if (!dockerfile) {
|
|
84
|
+
this.log(chalk.yellow('\n⚠️ Reanalysis is still in progress.'));
|
|
85
|
+
this.log(chalk.gray(' Check back later with:'));
|
|
86
|
+
this.log(chalk.cyan(` hyperdrive module dockerfile --slug ${flags.slug}`));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await this.displayResults(dockerfile, flags);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
console.error('Error:', error);
|
|
94
|
+
this.error(`Failed to reanalyze module ${flags.slug}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Build CloudWatch console URL for log viewing
|
|
99
|
+
*/
|
|
100
|
+
buildConsoleUrl(config) {
|
|
101
|
+
const encodedGroup = encodeURIComponent(encodeURIComponent(config.logGroup));
|
|
102
|
+
const encodedStream = encodeURIComponent(encodeURIComponent(config.logStream));
|
|
103
|
+
return `https://${config.region}.console.aws.amazon.com/cloudwatch/home?region=${config.region}#logsV2:log-groups/log-group/${encodedGroup}/log-events/${encodedStream}`;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Display analysis results
|
|
107
|
+
*/
|
|
108
|
+
async displayResults(dockerfile, flags) {
|
|
109
|
+
if (!dockerfile) {
|
|
110
|
+
this.log(chalk.yellow('\n⚠️ No Dockerfile found'));
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Show results
|
|
114
|
+
this.log(chalk.green('\n✅ Dockerfile regenerated successfully!\n'));
|
|
115
|
+
if (dockerfile.analysis) {
|
|
116
|
+
this.log(chalk.blue('📊 Analysis Results:'));
|
|
117
|
+
this.log(chalk.gray(` Runtime: ${dockerfile.analysis.detectedRuntime || 'unknown'}`));
|
|
118
|
+
this.log(chalk.gray(` Framework: ${dockerfile.analysis.detectedFramework || 'unknown'}`));
|
|
119
|
+
if (dockerfile.analysis.port) {
|
|
120
|
+
this.log(chalk.gray(` Port: ${dockerfile.analysis.port}`));
|
|
121
|
+
}
|
|
122
|
+
if (dockerfile.analysis.healthEndpoint) {
|
|
123
|
+
this.log(chalk.gray(` Health: ${dockerfile.analysis.healthEndpoint}`));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
this.log(chalk.blue('\n🐳 Validation Results:'));
|
|
127
|
+
this.log(chalk.gray(` Build: ${dockerfile.buildSuccess ? chalk.green('✓ Success') : chalk.red('✗ Failed')}`));
|
|
128
|
+
this.log(chalk.gray(` Run: ${dockerfile.runSuccess ? chalk.green('✓ Success') : chalk.red('✗ Failed')}`));
|
|
129
|
+
this.log(chalk.gray(` Port: ${dockerfile.portOpen ? chalk.green('✓ Open') : chalk.red('✗ Closed')}`));
|
|
130
|
+
if (dockerfile.analysis?.recommendations?.length) {
|
|
131
|
+
this.log(chalk.blue('\n💡 Recommendations:'));
|
|
132
|
+
for (const rec of dockerfile.analysis.recommendations) {
|
|
133
|
+
this.log(chalk.gray(` • ${rec}`));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.log(chalk.blue('\n📄 Regenerated Dockerfile:'));
|
|
137
|
+
this.log(chalk.gray('─'.repeat(60)));
|
|
138
|
+
this.log(dockerfile.dockerfile);
|
|
139
|
+
this.log(chalk.gray('─'.repeat(60)));
|
|
140
|
+
if (flags.save) {
|
|
141
|
+
writeFileSync('Dockerfile', dockerfile.dockerfile);
|
|
142
|
+
this.log(chalk.green('\n✅ Dockerfile saved to ./Dockerfile'));
|
|
143
|
+
if (dockerfile.dockerignore) {
|
|
144
|
+
writeFileSync('.dockerignore', dockerfile.dockerignore);
|
|
145
|
+
this.log(chalk.green('✅ .dockerignore saved to ./.dockerignore'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.log(chalk.gray('\nTip: Use --save to write files to current directory'));
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Poll for Dockerfile (fallback when streaming not available)
|
|
154
|
+
*/
|
|
155
|
+
async pollForDockerfile(service, slug, maxAttempts = 60, intervalMs = 5000) {
|
|
156
|
+
for (let i = 0; i < maxAttempts; i++) {
|
|
157
|
+
try {
|
|
158
|
+
const result = await service.moduleGetDockerfile(slug);
|
|
159
|
+
if (result) {
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
// Not ready yet
|
|
165
|
+
}
|
|
166
|
+
// Show progress
|
|
167
|
+
process.stdout.write(chalk.gray('.'));
|
|
168
|
+
await new Promise(resolve => setTimeout(resolve, intervalMs));
|
|
169
|
+
}
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Stream analysis logs using CloudWatch
|
|
174
|
+
*/
|
|
175
|
+
async streamAnalysisLogs(loggingConfig, flags, service) {
|
|
176
|
+
const tailer = new CloudWatchLogTailer(loggingConfig, {
|
|
177
|
+
pollInterval: 1000,
|
|
178
|
+
showDebug: false,
|
|
179
|
+
verbose: true,
|
|
180
|
+
});
|
|
181
|
+
this.log('');
|
|
182
|
+
const outcome = await tailer.tail();
|
|
183
|
+
this.log('');
|
|
184
|
+
if (outcome.success) {
|
|
185
|
+
this.log(chalk.green(`✅ ${outcome.message}`));
|
|
186
|
+
// Show CloudWatch console link
|
|
187
|
+
const consoleUrl = this.buildConsoleUrl(loggingConfig);
|
|
188
|
+
this.log(chalk.gray(` Logs: ${consoleUrl}`));
|
|
189
|
+
// Fetch generated Dockerfile
|
|
190
|
+
const dockerfile = await service.moduleGetDockerfile(flags.slug);
|
|
191
|
+
if (dockerfile) {
|
|
192
|
+
await this.displayResults(dockerfile, flags);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
this.log(chalk.yellow('\n⚠️ Dockerfile not found yet, may still be processing'));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
this.log(chalk.red(`❌ ${outcome.message}`));
|
|
200
|
+
// Show CloudWatch console link for debugging
|
|
201
|
+
const consoleUrl = this.buildConsoleUrl(loggingConfig);
|
|
202
|
+
this.log(chalk.gray(` Full logs: ${consoleUrl}`));
|
|
203
|
+
this.exit(1);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ModuleUpdate extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
buildCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
buildDirectory: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
buildFolder: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
buildRuntime: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
buildRuntimeVersion: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
11
|
+
ciService: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
defaultBranch: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
deploymentStrategy: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
14
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
15
|
+
framework: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
16
|
+
installCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
17
|
+
name: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
18
|
+
routeDiscovery: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
19
|
+
runCommand: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
20
|
+
runtime: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
21
|
+
runtimeVersion: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
22
|
+
slug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
23
|
+
sourceDirectory: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
24
|
+
sourceLocation: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
25
|
+
};
|
|
26
|
+
run(): Promise<void>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { Command, Flags } from '@oclif/core';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { HyperdriveSigV4Service } from '../../services/hyperdrive-sigv4.js';
|
|
4
|
+
export default class ModuleUpdate extends Command {
|
|
5
|
+
static description = 'Update a module/project configuration';
|
|
6
|
+
static examples = [
|
|
7
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --runtimeVersion="12"',
|
|
8
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --buildCommand="npm run build:prod"',
|
|
9
|
+
'<%= config.bin %> <%= command.id %> --slug="my-module" --name="New Name" --framework="React.js"',
|
|
10
|
+
];
|
|
11
|
+
static flags = {
|
|
12
|
+
buildCommand: Flags.string({
|
|
13
|
+
description: 'Build command',
|
|
14
|
+
}),
|
|
15
|
+
buildDirectory: Flags.string({
|
|
16
|
+
description: 'Build output directory (e.g., dist, build, .next) - where compiled artifacts are output',
|
|
17
|
+
}),
|
|
18
|
+
buildFolder: Flags.string({
|
|
19
|
+
description: 'Build folder',
|
|
20
|
+
}),
|
|
21
|
+
buildRuntime: Flags.string({
|
|
22
|
+
description: 'Build runtime for Dockerfile template selection',
|
|
23
|
+
options: ['nodejs', 'python', 'go', 'rust', 'java', 'dotnet', 'ruby'],
|
|
24
|
+
}),
|
|
25
|
+
buildRuntimeVersion: Flags.string({
|
|
26
|
+
description: 'Build runtime version (e.g., 20 for Node, 3.12 for Python)',
|
|
27
|
+
}),
|
|
28
|
+
ciService: Flags.string({
|
|
29
|
+
description: 'CI service used',
|
|
30
|
+
options: ['github-actions', 'gitlab-ci', 'circle-ci', 'jenkins'],
|
|
31
|
+
}),
|
|
32
|
+
defaultBranch: Flags.string({
|
|
33
|
+
description: 'Default git branch to branch from (e.g., main, master)',
|
|
34
|
+
}),
|
|
35
|
+
deploymentStrategy: Flags.string({
|
|
36
|
+
description: 'Deployment strategy: "serverless" for Serverless Framework deployment to Lambda',
|
|
37
|
+
options: ['serverless'],
|
|
38
|
+
}),
|
|
39
|
+
domain: Flags.string({
|
|
40
|
+
char: 'd',
|
|
41
|
+
description: 'Tenant domain (for multi-domain setups)',
|
|
42
|
+
}),
|
|
43
|
+
framework: Flags.string({
|
|
44
|
+
description: 'Framework used',
|
|
45
|
+
options: ['express', 'fastify', 'koa', 'nextjs', 'react', 'vue', 'angular', 'svelte', 'flask', 'django', 'fastapi', 'gin', 'actix', 'spring', 'aspnet', 'rails', 'other'],
|
|
46
|
+
}),
|
|
47
|
+
installCommand: Flags.string({
|
|
48
|
+
description: 'Install command',
|
|
49
|
+
}),
|
|
50
|
+
name: Flags.string({
|
|
51
|
+
description: 'Name of the project',
|
|
52
|
+
}),
|
|
53
|
+
routeDiscovery: Flags.boolean({
|
|
54
|
+
allowNo: true,
|
|
55
|
+
description: 'Enable AI-powered route discovery for per-route Lambda functions',
|
|
56
|
+
}),
|
|
57
|
+
runCommand: Flags.string({
|
|
58
|
+
description: 'Run command',
|
|
59
|
+
}),
|
|
60
|
+
runtime: Flags.string({
|
|
61
|
+
char: 'r',
|
|
62
|
+
description: 'Runtime environment',
|
|
63
|
+
options: ['nodejs', 'python', 'go', 'rust', 'java', 'dotnet', 'ruby'],
|
|
64
|
+
}),
|
|
65
|
+
runtimeVersion: Flags.string({
|
|
66
|
+
char: 'v',
|
|
67
|
+
description: 'Runtime version (e.g., 20, 3.12, 1.21)',
|
|
68
|
+
}),
|
|
69
|
+
slug: Flags.string({
|
|
70
|
+
char: 's',
|
|
71
|
+
description: 'Module slug to update (required)',
|
|
72
|
+
required: true,
|
|
73
|
+
}),
|
|
74
|
+
sourceDirectory: Flags.string({
|
|
75
|
+
description: 'Source code directory (e.g., src, lib, . for root) - default: src for Lambda, . for static',
|
|
76
|
+
}),
|
|
77
|
+
sourceLocation: Flags.string({
|
|
78
|
+
description: 'Source location of the project',
|
|
79
|
+
}),
|
|
80
|
+
};
|
|
81
|
+
async run() {
|
|
82
|
+
const { flags } = await this.parse(ModuleUpdate);
|
|
83
|
+
const { slug, ...updateFields } = flags;
|
|
84
|
+
// Filter out undefined values
|
|
85
|
+
const updateData = Object.fromEntries(Object.entries(updateFields).filter(([_, value]) => value !== undefined));
|
|
86
|
+
if (Object.keys(updateData).length === 0) {
|
|
87
|
+
this.log(chalk.yellow('No fields to update. Please provide at least one field to update.'));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.log(chalk.blue(`🔄 Updating module "${slug}"...`));
|
|
91
|
+
const service = new HyperdriveSigV4Service(flags.domain);
|
|
92
|
+
try {
|
|
93
|
+
const result = await service.moduleUpdate({ slug, ...updateData });
|
|
94
|
+
this.log(chalk.green('✅ Module updated successfully!'));
|
|
95
|
+
this.log(JSON.stringify(result, null, 2));
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
console.error('Error:', error);
|
|
99
|
+
this.error('An error occurred while updating the module');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from '@oclif/core';
|
|
2
|
+
export default class ParameterAdd extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
accountId: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
7
|
+
domain: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
8
|
+
key: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
9
|
+
moduleSlug: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
10
|
+
specific: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
stage: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
12
|
+
value: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|