@sembix/cli 1.9.0 → 1.10.0
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/config.example.yaml +3 -2
- package/dist/commands/setup.js +4 -5
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/update.js +6 -8
- package/dist/commands/update.js.map +1 -1
- package/dist/config-fields.js +9 -9
- package/dist/config-fields.js.map +1 -1
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.d.ts.map +1 -1
- package/dist/config-schema.js +2 -2
- package/dist/config-schema.js.map +1 -1
- package/dist/prompts/environment-setup.d.ts.map +1 -1
- package/dist/prompts/environment-setup.js +606 -405
- package/dist/prompts/environment-setup.js.map +1 -1
- package/dist/prompts/prompt-helpers.d.ts +7 -0
- package/dist/prompts/prompt-helpers.d.ts.map +1 -1
- package/dist/prompts/prompt-helpers.js +18 -1
- package/dist/prompts/prompt-helpers.js.map +1 -1
- package/dist/sembix-cli-1.10.0.tgz +0 -0
- package/dist/types.d.ts +5 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/config-loader.d.ts.map +1 -1
- package/dist/utils/config-loader.js +7 -1
- package/dist/utils/config-loader.js.map +1 -1
- package/dist/utils/github.d.ts.map +1 -1
- package/dist/utils/github.js +6 -0
- package/dist/utils/github.js.map +1 -1
- package/dist/utils/ui.d.ts +5 -1
- package/dist/utils/ui.d.ts.map +1 -1
- package/dist/utils/ui.js +49 -7
- package/dist/utils/ui.js.map +1 -1
- package/package.json +1 -1
- package/dist/sembix-cli-1.9.0.tgz +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { input, select, confirm } from '@inquirer/prompts';
|
|
2
|
-
import { awsRegions, awsAccountIdSchema, iamRoleArnSchema, acmCertArnSchema, route53ZoneIdSchema } from '../types.js';
|
|
2
|
+
import { awsRegions, awsAccountIdSchema, iamRoleArnSchema, acmCertArnSchema, route53ZoneIdSchema, secretsManagerArnSchema } from '../types.js';
|
|
3
3
|
import * as ui from '../utils/ui.js';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import { promptIfMissing } from './prompt-helpers.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// ============================================================
|
|
7
|
+
// HELPERS
|
|
8
|
+
// ============================================================
|
|
9
9
|
function explainField(title, description, example) {
|
|
10
10
|
console.log();
|
|
11
11
|
console.log(chalk.cyan('❓ ' + chalk.bold(title)));
|
|
@@ -15,88 +15,129 @@ function explainField(title, description, example) {
|
|
|
15
15
|
}
|
|
16
16
|
console.log();
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
function hasAdvancedFields(partialConfig) {
|
|
19
|
+
if (!partialConfig)
|
|
20
|
+
return false;
|
|
21
|
+
const { security, tls, networking } = partialConfig;
|
|
22
|
+
return !!(security?.useCustomSecurityGroups ||
|
|
23
|
+
security?.useCustomIamPolicies ||
|
|
24
|
+
security?.customSecurityGroups ||
|
|
25
|
+
security?.customIamRoles ||
|
|
26
|
+
security?.workflowRunsKeyAlias ||
|
|
27
|
+
tls?.certificateArn ||
|
|
28
|
+
tls?.bffAlbInternal ||
|
|
29
|
+
tls?.bffInternalAlbCertificateArn ||
|
|
30
|
+
tls?.privateCaCertSecretArn ||
|
|
31
|
+
networking?.useCustomNetworking);
|
|
32
|
+
}
|
|
33
|
+
function getStepCount(mode) {
|
|
34
|
+
return mode === 'express' ? 4 : 8;
|
|
35
|
+
}
|
|
36
|
+
// ============================================================
|
|
37
|
+
// STEP: MODE SELECTION
|
|
38
|
+
// ============================================================
|
|
39
|
+
async function promptModeSelection(partialConfig) {
|
|
40
|
+
if (hasAdvancedFields(partialConfig)) {
|
|
41
|
+
ui.info('Advanced mode auto-selected (config contains advanced fields)');
|
|
42
|
+
console.log();
|
|
43
|
+
return 'advanced';
|
|
44
|
+
}
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.dim(' Choose how you\'d like to configure your environment:'));
|
|
47
|
+
console.log();
|
|
48
|
+
return select({
|
|
49
|
+
message: 'Select setup mode:',
|
|
50
|
+
choices: [
|
|
51
|
+
{
|
|
52
|
+
name: 'Express (Recommended) — Quick setup with sensible defaults (~12 questions)',
|
|
53
|
+
value: 'express',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
name: 'Advanced — Full control over all configuration options (~35+ questions)',
|
|
57
|
+
value: 'advanced',
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
default: 'express',
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
// ============================================================
|
|
64
|
+
// STEP: REPOSITORY SELECTION
|
|
65
|
+
// ============================================================
|
|
66
|
+
async function promptRepository(ctx, stepNumber, providedRepo) {
|
|
67
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Repository Selection');
|
|
68
|
+
console.log(chalk.dim(' Select the GitHub repository where your Sembix Studio deployment will be configured.'));
|
|
69
|
+
console.log(chalk.dim(' This repository contains your deployment workflows and configuration files.'));
|
|
22
70
|
console.log();
|
|
23
|
-
// Step 1: Repository Selection
|
|
24
|
-
let repositoryChoice;
|
|
25
|
-
let owner;
|
|
26
|
-
let repo;
|
|
27
71
|
if (providedRepo) {
|
|
28
|
-
// Repository provided via CLI argument - validate it exists
|
|
29
72
|
const parts = providedRepo.split('/');
|
|
30
73
|
if (parts.length !== 2) {
|
|
31
74
|
ui.error('Invalid repository format. Use: owner/repo');
|
|
32
75
|
process.exit(1);
|
|
33
76
|
}
|
|
34
|
-
[owner, repo] = parts;
|
|
35
|
-
// Verify repository exists
|
|
77
|
+
const [owner, repo] = parts;
|
|
36
78
|
try {
|
|
37
|
-
await githubClient.getRepository(owner, repo);
|
|
38
|
-
|
|
39
|
-
ui.info(`Using repository: ${ui.highlight(repositoryChoice)}`);
|
|
79
|
+
await ctx.githubClient.getRepository(owner, repo);
|
|
80
|
+
ui.info(`Using repository: ${ui.highlight(providedRepo)}`);
|
|
40
81
|
console.log();
|
|
82
|
+
return { owner, repo };
|
|
41
83
|
}
|
|
42
84
|
catch {
|
|
43
85
|
ui.error(`Repository '${providedRepo}' not found or not accessible.`);
|
|
44
86
|
console.log();
|
|
45
|
-
console.log(ui.dim('
|
|
46
|
-
console.log(ui.dim('
|
|
47
|
-
console.log(ui.dim('
|
|
87
|
+
console.log(ui.dim(' Make sure:'));
|
|
88
|
+
console.log(ui.dim(' - The repository name is correct (owner/repo)'));
|
|
89
|
+
console.log(ui.dim(' - Your GitHub token has access to this repository'));
|
|
48
90
|
process.exit(1);
|
|
49
91
|
}
|
|
50
92
|
}
|
|
93
|
+
const repos = await ctx.githubClient.listRepositories();
|
|
94
|
+
if (repos.length === 0) {
|
|
95
|
+
ui.error('No repositories found for your account.');
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(ui.dim(' Tip: Make sure your GitHub token has access to the repositories you want to manage.'));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
let repositoryChoice;
|
|
101
|
+
if (repos.length === 1) {
|
|
102
|
+
repositoryChoice = repos[0].full_name;
|
|
103
|
+
ui.info(`Using repository: ${ui.highlight(repositoryChoice)}`);
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
51
106
|
else {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
if (repos.length === 1) {
|
|
61
|
-
repositoryChoice = repos[0].full_name;
|
|
62
|
-
ui.info(`Using repository: ${ui.highlight(repositoryChoice)}`);
|
|
63
|
-
console.log();
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
repositoryChoice = await select({
|
|
67
|
-
message: 'Select your deployment repository:',
|
|
68
|
-
choices: repos.map(repo => ({
|
|
69
|
-
name: repo.full_name,
|
|
70
|
-
value: repo.full_name,
|
|
71
|
-
})),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
[owner, repo] = repositoryChoice.split('/');
|
|
107
|
+
repositoryChoice = await select({
|
|
108
|
+
message: 'Select your deployment repository:',
|
|
109
|
+
choices: repos.map(r => ({
|
|
110
|
+
name: r.full_name,
|
|
111
|
+
value: r.full_name,
|
|
112
|
+
})),
|
|
113
|
+
});
|
|
75
114
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
115
|
+
const [owner, repo] = repositoryChoice.split('/');
|
|
116
|
+
return { owner, repo };
|
|
117
|
+
}
|
|
118
|
+
// ============================================================
|
|
119
|
+
// STEP: BASIC CONFIGURATION
|
|
120
|
+
// ============================================================
|
|
121
|
+
async function promptBasicConfig(ctx, stepNumber, owner, repo, providedEnvironmentName) {
|
|
122
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Basic Configuration');
|
|
123
|
+
console.log(chalk.dim(' Configure the basic settings for your Sembix Studio environment.'));
|
|
124
|
+
console.log(chalk.dim(' These settings tell us where to deploy and how to connect to AWS.'));
|
|
82
125
|
console.log();
|
|
83
126
|
// Environment Name
|
|
84
127
|
let environmentName;
|
|
85
128
|
if (providedEnvironmentName) {
|
|
86
|
-
// Environment name provided via CLI argument - validate it
|
|
87
129
|
if (!/^[a-z0-9-]+$/.test(providedEnvironmentName) || providedEnvironmentName.length < 3) {
|
|
88
130
|
ui.error('Invalid environment name. Must be lowercase letters, numbers, and hyphens (min 3 chars).');
|
|
89
131
|
process.exit(1);
|
|
90
132
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const envExists = await githubClient.environmentExists(owner, repo, providedEnvironmentName);
|
|
133
|
+
if (!ctx.isUpdate) {
|
|
134
|
+
const envExists = await ctx.githubClient.environmentExists(owner, repo, providedEnvironmentName);
|
|
94
135
|
if (envExists) {
|
|
95
136
|
ui.error(`Environment '${providedEnvironmentName}' already exists in ${owner}/${repo}`);
|
|
96
137
|
console.log();
|
|
97
|
-
console.log(ui.dim('
|
|
98
|
-
console.log(ui.dim('
|
|
99
|
-
console.log(ui.dim('
|
|
138
|
+
console.log(ui.dim(' Tip:'));
|
|
139
|
+
console.log(ui.dim(' - Use a different environment name'));
|
|
140
|
+
console.log(ui.dim(' - Or update the existing environment with: sembix studio update'));
|
|
100
141
|
process.exit(1);
|
|
101
142
|
}
|
|
102
143
|
}
|
|
@@ -105,7 +146,6 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
105
146
|
console.log();
|
|
106
147
|
}
|
|
107
148
|
else {
|
|
108
|
-
// Interactive environment name prompt
|
|
109
149
|
explainField('Environment Name', 'This is the name of your GitHub Actions environment. It should be unique and descriptive.\nUse lowercase letters, numbers, and hyphens only (no spaces or special characters).', 'client-abc-production or acme-staging');
|
|
110
150
|
environmentName = await input({
|
|
111
151
|
message: 'Environment name:',
|
|
@@ -122,7 +162,7 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
122
162
|
});
|
|
123
163
|
}
|
|
124
164
|
// AWS Account ID
|
|
125
|
-
const awsAccountId = await promptIfMissing(partialConfig?.awsAccountId, async () => {
|
|
165
|
+
const awsAccountId = await promptIfMissing(ctx.partialConfig?.awsAccountId, async () => {
|
|
126
166
|
explainField('AWS Account ID', 'This is your 12-digit AWS account number where Sembix Studio will be deployed.\nYou can find this in the AWS Console by clicking on your account name in the top right.', '123456789012');
|
|
127
167
|
return input({
|
|
128
168
|
message: 'AWS Account ID (12 digits):',
|
|
@@ -137,7 +177,7 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
137
177
|
});
|
|
138
178
|
}, 'AWS Account ID');
|
|
139
179
|
// AWS Region
|
|
140
|
-
const awsRegion = await promptIfMissing(partialConfig?.awsRegion, async () => {
|
|
180
|
+
const awsRegion = await promptIfMissing(ctx.partialConfig?.awsRegion, async () => {
|
|
141
181
|
explainField('AWS Region', 'Choose the AWS region where you want to deploy Sembix Studio.\nPick a region close to your users for better performance.', 'us-east-1 (Virginia) or eu-west-1 (Ireland)');
|
|
142
182
|
return select({
|
|
143
183
|
message: 'AWS Region:',
|
|
@@ -149,7 +189,7 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
149
189
|
});
|
|
150
190
|
}, 'AWS Region');
|
|
151
191
|
// GitHub Actions Role ARN
|
|
152
|
-
const customerRoleArn = await promptIfMissing(partialConfig?.customerRoleArn, async () => {
|
|
192
|
+
const customerRoleArn = await promptIfMissing(ctx.partialConfig?.customerRoleArn, async () => {
|
|
153
193
|
explainField('GitHub Actions Deployment Role ARN', 'This is the IAM role that GitHub Actions will assume to deploy infrastructure to your AWS account.\nIt must have permissions to create and manage AWS resources (VPCs, ECS, RDS, etc.).', 'arn:aws:iam::123456789012:role/GitHubActionsDeployRole');
|
|
154
194
|
return input({
|
|
155
195
|
message: 'GitHub Actions Role ARN:',
|
|
@@ -164,7 +204,7 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
164
204
|
});
|
|
165
205
|
}, 'GitHub Actions Role ARN');
|
|
166
206
|
// Terraform State Bucket
|
|
167
|
-
const terraformStateBucket = await promptIfMissing(partialConfig?.terraformStateBucket, async () => {
|
|
207
|
+
const terraformStateBucket = await promptIfMissing(ctx.partialConfig?.terraformStateBucket, async () => {
|
|
168
208
|
explainField('Terraform State S3 Bucket', 'This is the S3 bucket where Terraform will store its state file.\nThe state file keeps track of all the infrastructure Terraform creates.\nMake sure this bucket already exists in your AWS account.', 'my-terraform-state-bucket');
|
|
169
209
|
return input({
|
|
170
210
|
message: 'Terraform State S3 Bucket name:',
|
|
@@ -178,91 +218,110 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
178
218
|
},
|
|
179
219
|
});
|
|
180
220
|
}, 'Terraform State Bucket');
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
221
|
+
return { environmentName, awsAccountId, awsRegion, customerRoleArn, terraformStateBucket };
|
|
222
|
+
}
|
|
223
|
+
// ============================================================
|
|
224
|
+
// STEP: DATABASE CONFIGURATION
|
|
225
|
+
// ============================================================
|
|
226
|
+
async function promptDatabase(ctx, stepNumber) {
|
|
227
|
+
const { partialConfig } = ctx;
|
|
228
|
+
// Config always wins
|
|
186
229
|
if (partialConfig?.database?.name && partialConfig?.database?.user) {
|
|
187
|
-
|
|
188
|
-
databaseName = partialConfig.database.name;
|
|
189
|
-
databaseUser = partialConfig.database.user;
|
|
190
|
-
ui.info(`Using Database: ${ui.highlight(databaseName)} / ${ui.highlight(databaseUser)} (from config)`);
|
|
230
|
+
ui.info(`Using Database: ${ui.highlight(partialConfig.database.name)} / ${ui.highlight(partialConfig.database.user)} (from config)`);
|
|
191
231
|
console.log();
|
|
232
|
+
return { name: partialConfig.database.name, user: partialConfig.database.user };
|
|
192
233
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
234
|
+
// Express mode: use defaults
|
|
235
|
+
if (ctx.mode === 'express') {
|
|
236
|
+
const name = partialConfig?.database?.name ?? 'sembix_studio';
|
|
237
|
+
const user = partialConfig?.database?.user ?? 'sembix_studio_user';
|
|
238
|
+
ui.info(`Using default Database: ${ui.highlight(name)} / ${ui.highlight(user)}`);
|
|
197
239
|
console.log();
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
240
|
+
return { name, user };
|
|
241
|
+
}
|
|
242
|
+
// Advanced mode: prompt
|
|
243
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Database Configuration');
|
|
244
|
+
console.log(chalk.dim(' Configure the PostgreSQL database settings for Sembix Studio.'));
|
|
245
|
+
console.log(chalk.dim(' The database stores all your Studio data (workflows, users, etc.).'));
|
|
246
|
+
console.log();
|
|
247
|
+
const name = await promptIfMissing(partialConfig?.database?.name, async () => {
|
|
248
|
+
explainField('Database Name', 'The name of the PostgreSQL database that will be created for Sembix Studio.\nMust start with a letter and contain only letters, numbers, and underscores.', 'sembix_studio or studio_production');
|
|
249
|
+
return input({
|
|
250
|
+
message: 'Database name:',
|
|
251
|
+
default: 'sembix_studio',
|
|
252
|
+
validate: (value) => {
|
|
253
|
+
if (!value)
|
|
254
|
+
return 'Database name is required';
|
|
255
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(value)) {
|
|
256
|
+
return 'Must start with a letter and contain only letters, numbers, and underscores';
|
|
257
|
+
}
|
|
258
|
+
return true;
|
|
259
|
+
},
|
|
260
|
+
});
|
|
261
|
+
}, 'Database name');
|
|
262
|
+
const user = await promptIfMissing(partialConfig?.database?.user, async () => {
|
|
263
|
+
explainField('Database User', 'The PostgreSQL username that Sembix Studio will use to connect to the database.\nMust start with a letter and contain only letters, numbers, and underscores.', 'sembix_studio_user or studio_app');
|
|
264
|
+
return input({
|
|
265
|
+
message: 'Database user:',
|
|
266
|
+
default: 'sembix_studio_user',
|
|
267
|
+
validate: (value) => {
|
|
268
|
+
if (!value)
|
|
269
|
+
return 'Database user is required';
|
|
270
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(value)) {
|
|
271
|
+
return 'Must start with a letter and contain only letters, numbers, and underscores';
|
|
272
|
+
}
|
|
273
|
+
return true;
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
}, 'Database user');
|
|
277
|
+
return { name, user };
|
|
278
|
+
}
|
|
279
|
+
// ============================================================
|
|
280
|
+
// STEP: NETWORKING & INFRASTRUCTURE
|
|
281
|
+
// ============================================================
|
|
282
|
+
async function promptNetworking(ctx, stepNumber) {
|
|
283
|
+
// Express mode: use new VPC with all defaults
|
|
284
|
+
if (ctx.mode === 'express') {
|
|
285
|
+
ui.info('Using default Networking: New VPC (10.0.0.0/16, VPC endpoints enabled)');
|
|
286
|
+
console.log();
|
|
287
|
+
return {
|
|
288
|
+
enableVpcEndpoints: true,
|
|
289
|
+
useCustomNetworking: false,
|
|
290
|
+
vpcCidr: '10.0.0.0/16',
|
|
291
|
+
publicSubnetCidrs: '["10.0.0.0/24","10.0.3.0/24"]',
|
|
292
|
+
privateSubnetCidrs: '["10.0.1.0/24","10.0.2.0/24"]',
|
|
293
|
+
azCount: '2',
|
|
294
|
+
};
|
|
230
295
|
}
|
|
231
|
-
//
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log(chalk.dim('🌐 Configure networking settings for your Sembix Studio deployment.'));
|
|
236
|
-
console.log(chalk.dim(' You can either create a new VPC or use an existing one.'));
|
|
296
|
+
// Advanced mode: prompt
|
|
297
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Networking & Infrastructure');
|
|
298
|
+
console.log(chalk.dim(' Configure networking settings for your Sembix Studio deployment.'));
|
|
299
|
+
console.log(chalk.dim(' You can either create a new VPC or use an existing one.'));
|
|
237
300
|
console.log();
|
|
238
301
|
// VPC Endpoints
|
|
239
302
|
explainField('VPC Endpoints', 'VPC endpoints allow your AWS resources to communicate with AWS services privately (without going through the internet).\nThis improves security and can reduce data transfer costs.\n\n' +
|
|
240
|
-
chalk.green('
|
|
303
|
+
chalk.green(' Recommended: ') + 'Enable this unless you have a specific reason not to.', 'Choose Yes (recommended)');
|
|
241
304
|
const enableVpcEndpoints = await confirm({
|
|
242
305
|
message: 'Enable VPC endpoints for AWS services?',
|
|
243
306
|
default: true,
|
|
244
307
|
});
|
|
245
308
|
// Custom Networking
|
|
246
309
|
console.log();
|
|
247
|
-
console.log(chalk.cyan('
|
|
310
|
+
console.log(chalk.cyan(' ' + chalk.bold('Use Existing VPC?')));
|
|
248
311
|
console.log(chalk.dim(' You have two options:'));
|
|
249
|
-
console.log(chalk.dim('
|
|
250
|
-
console.log(chalk.dim('
|
|
312
|
+
console.log(chalk.dim(' - ' + chalk.white('No (Create New)') + ' - We\'ll create a brand new VPC with all necessary networking (recommended for new deployments)'));
|
|
313
|
+
console.log(chalk.dim(' - ' + chalk.white('Yes (Use Existing)') + ' - You provide an existing VPC and subnets (for integrating with existing infrastructure)'));
|
|
251
314
|
console.log();
|
|
252
315
|
const useCustomNetworking = await confirm({
|
|
253
316
|
message: 'Do you want to use an existing VPC?',
|
|
254
317
|
default: false,
|
|
255
318
|
});
|
|
256
|
-
let networking;
|
|
257
319
|
if (useCustomNetworking) {
|
|
258
|
-
// ============================================================
|
|
259
|
-
// CUSTOM NETWORKING (Existing VPC)
|
|
260
|
-
// ============================================================
|
|
261
320
|
console.log();
|
|
262
|
-
console.log(chalk.yellow('
|
|
263
|
-
console.log(chalk.dim('
|
|
321
|
+
console.log(chalk.yellow(' Using Existing VPC'));
|
|
322
|
+
console.log(chalk.dim(' You\'ll need to provide your VPC ID and subnet IDs from the AWS Console.'));
|
|
264
323
|
console.log();
|
|
265
|
-
explainField('VPC ID', 'The ID of your existing Virtual Private Cloud.\nYou can find this in the AWS Console under VPC
|
|
324
|
+
explainField('VPC ID', 'The ID of your existing Virtual Private Cloud.\nYou can find this in the AWS Console under VPC > Your VPCs.', 'vpc-0123456789abcdef0');
|
|
266
325
|
const customVpcId = await input({
|
|
267
326
|
message: 'VPC ID:',
|
|
268
327
|
validate: (value) => {
|
|
@@ -299,7 +358,7 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
299
358
|
return true;
|
|
300
359
|
},
|
|
301
360
|
});
|
|
302
|
-
|
|
361
|
+
return {
|
|
303
362
|
enableVpcEndpoints,
|
|
304
363
|
useCustomNetworking: true,
|
|
305
364
|
customVpcId,
|
|
@@ -307,108 +366,117 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
307
366
|
customPrivateSubnetIds,
|
|
308
367
|
};
|
|
309
368
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
369
|
+
// New VPC Creation
|
|
370
|
+
console.log();
|
|
371
|
+
console.log(chalk.green(' Creating New VPC'));
|
|
372
|
+
console.log(chalk.dim(' We\'ll create a new VPC with all necessary networking components.'));
|
|
373
|
+
console.log(chalk.dim(' You can use the default values or customize them.'));
|
|
374
|
+
console.log();
|
|
375
|
+
explainField('VPC CIDR Block', 'This is the IP address range for your entire VPC (in CIDR notation).\nThe default (10.0.0.0/16) gives you 65,536 IP addresses, which is plenty for most deployments.\n\n' +
|
|
376
|
+
chalk.green(' Tip: ') + 'Press Enter to use the default unless you have specific networking requirements.', '10.0.0.0/16 (default, recommended)');
|
|
377
|
+
const vpcCidr = await input({
|
|
378
|
+
message: 'VPC CIDR Block:',
|
|
379
|
+
default: '10.0.0.0/16',
|
|
380
|
+
validate: (value) => {
|
|
381
|
+
if (!value)
|
|
382
|
+
return 'VPC CIDR is required';
|
|
383
|
+
if (!/^\d+\.\d+\.\d+\.\d+\/\d+$/.test(value)) {
|
|
384
|
+
return 'Must be a valid CIDR block (e.g., 10.0.0.0/16)';
|
|
385
|
+
}
|
|
386
|
+
return true;
|
|
387
|
+
},
|
|
388
|
+
});
|
|
389
|
+
explainField('Public Subnet CIDRs', 'IP ranges for public subnets (have internet access). Format as a JSON array.\nEach subnet will be created in a different availability zone for high availability.\n\n' +
|
|
390
|
+
chalk.green(' Tip: ') + 'Press Enter to use the defaults.', '["10.0.0.0/24","10.0.3.0/24"]');
|
|
391
|
+
const publicSubnetCidrs = await input({
|
|
392
|
+
message: 'Public Subnet CIDRs (JSON array):',
|
|
393
|
+
default: '["10.0.0.0/24","10.0.3.0/24"]',
|
|
394
|
+
validate: (value) => {
|
|
395
|
+
if (!value)
|
|
396
|
+
return 'Public subnet CIDRs are required';
|
|
397
|
+
try {
|
|
398
|
+
const parsed = JSON.parse(value);
|
|
399
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
400
|
+
return 'Must be a non-empty JSON array';
|
|
329
401
|
}
|
|
330
402
|
return true;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
return 'Must be valid JSON array (e.g., ["10.0.0.0/24","10.0.3.0/24"])';
|
|
350
|
-
}
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
explainField('Private Subnet CIDRs', 'IP ranges for private subnets (no direct internet access). Format as a JSON array.\nThese are used for databases and application servers that don\'t need public access.\n\n' +
|
|
354
|
-
chalk.green('💡 Tip: ') + 'Press Enter to use the defaults.', '["10.0.1.0/24","10.0.2.0/24"]');
|
|
355
|
-
const privateSubnetCidrs = await input({
|
|
356
|
-
message: 'Private Subnet CIDRs (JSON array):',
|
|
357
|
-
default: '["10.0.1.0/24","10.0.2.0/24"]',
|
|
358
|
-
validate: (value) => {
|
|
359
|
-
if (!value)
|
|
360
|
-
return 'Private subnet CIDRs are required';
|
|
361
|
-
try {
|
|
362
|
-
const parsed = JSON.parse(value);
|
|
363
|
-
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
364
|
-
return 'Must be a non-empty JSON array';
|
|
365
|
-
}
|
|
366
|
-
return true;
|
|
367
|
-
}
|
|
368
|
-
catch {
|
|
369
|
-
return 'Must be valid JSON array (e.g., ["10.0.1.0/24","10.0.2.0/24"])';
|
|
403
|
+
}
|
|
404
|
+
catch {
|
|
405
|
+
return 'Must be valid JSON array (e.g., ["10.0.0.0/24","10.0.3.0/24"])';
|
|
406
|
+
}
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
explainField('Private Subnet CIDRs', 'IP ranges for private subnets (no direct internet access). Format as a JSON array.\nThese are used for databases and application servers that don\'t need public access.\n\n' +
|
|
410
|
+
chalk.green(' Tip: ') + 'Press Enter to use the defaults.', '["10.0.1.0/24","10.0.2.0/24"]');
|
|
411
|
+
const privateSubnetCidrs = await input({
|
|
412
|
+
message: 'Private Subnet CIDRs (JSON array):',
|
|
413
|
+
default: '["10.0.1.0/24","10.0.2.0/24"]',
|
|
414
|
+
validate: (value) => {
|
|
415
|
+
if (!value)
|
|
416
|
+
return 'Private subnet CIDRs are required';
|
|
417
|
+
try {
|
|
418
|
+
const parsed = JSON.parse(value);
|
|
419
|
+
if (!Array.isArray(parsed) || parsed.length === 0) {
|
|
420
|
+
return 'Must be a non-empty JSON array';
|
|
370
421
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
422
|
+
return true;
|
|
423
|
+
}
|
|
424
|
+
catch {
|
|
425
|
+
return 'Must be valid JSON array (e.g., ["10.0.1.0/24","10.0.2.0/24"])';
|
|
426
|
+
}
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
explainField('Availability Zones', 'AWS Availability Zones are isolated locations within a region.\nUsing multiple AZs provides high availability and fault tolerance.\n\n' +
|
|
430
|
+
chalk.green(' Recommended: ') + '2 AZs is sufficient for most deployments.', '2 (recommended)');
|
|
431
|
+
const azCount = await select({
|
|
432
|
+
message: 'Number of Availability Zones:',
|
|
433
|
+
choices: [
|
|
434
|
+
{ name: '2 (recommended - good balance of cost and availability)', value: '2' },
|
|
435
|
+
{ name: '3 (maximum availability)', value: '3' },
|
|
436
|
+
],
|
|
437
|
+
default: '2',
|
|
438
|
+
});
|
|
439
|
+
return {
|
|
440
|
+
enableVpcEndpoints,
|
|
441
|
+
useCustomNetworking: false,
|
|
442
|
+
vpcCidr,
|
|
443
|
+
publicSubnetCidrs,
|
|
444
|
+
privateSubnetCidrs,
|
|
445
|
+
azCount,
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
// ============================================================
|
|
449
|
+
// STEP: SECURITY & IAM
|
|
450
|
+
// ============================================================
|
|
451
|
+
async function promptSecurity(ctx, stepNumber) {
|
|
452
|
+
// Express mode: skip entirely
|
|
453
|
+
if (ctx.mode === 'express') {
|
|
454
|
+
ui.info('Using default Security: No custom security groups, no custom IAM roles');
|
|
455
|
+
console.log();
|
|
456
|
+
return {
|
|
457
|
+
useCustomSecurityGroups: false,
|
|
458
|
+
useCustomIamPolicies: false,
|
|
390
459
|
};
|
|
391
460
|
}
|
|
392
|
-
//
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
console.log(chalk.dim('🔒 Configure advanced security settings.'));
|
|
397
|
-
console.log(chalk.dim(' These are optional. Most users can skip this section by pressing Enter.'));
|
|
461
|
+
// Advanced mode: prompt
|
|
462
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Security & IAM (Optional)');
|
|
463
|
+
console.log(chalk.dim(' Configure advanced security settings.'));
|
|
464
|
+
console.log(chalk.dim(' These are optional. Most users can skip this section by pressing Enter.'));
|
|
398
465
|
console.log();
|
|
399
466
|
// KMS Key
|
|
400
467
|
explainField('Workflow Runs KMS Key (Optional)', 'AWS KMS (Key Management Service) key for encrypting workflow execution data.\nLeave blank to use AWS-managed encryption (recommended for most users).\n\n' +
|
|
401
|
-
chalk.green('
|
|
468
|
+
chalk.green(' Tip: ') + 'Press Enter to skip unless you have specific compliance requirements.', 'alias/my-kms-key or leave blank');
|
|
402
469
|
const workflowRunsKeyAlias = await input({
|
|
403
470
|
message: 'Workflow Runs KMS Key Alias (press Enter to skip):',
|
|
404
471
|
default: '',
|
|
405
472
|
});
|
|
406
473
|
// Custom Security Groups
|
|
407
474
|
console.log();
|
|
408
|
-
console.log(chalk.cyan('
|
|
475
|
+
console.log(chalk.cyan(' ' + chalk.bold('Custom Security Groups')));
|
|
409
476
|
console.log(chalk.dim(' Security groups control network traffic to/from AWS resources.'));
|
|
410
|
-
console.log(chalk.dim('
|
|
411
|
-
console.log(chalk.dim('
|
|
477
|
+
console.log(chalk.dim(' - ' + chalk.white('No') + ' - We\'ll create new security groups with proper rules (recommended)'));
|
|
478
|
+
console.log(chalk.dim(' - ' + chalk.white('Yes') + ' - You provide existing security group IDs (for advanced users)'));
|
|
479
|
+
ui.costSignal('Selecting Yes will ask for 8 security group IDs');
|
|
412
480
|
console.log();
|
|
413
481
|
const useCustomSecurityGroups = await confirm({
|
|
414
482
|
message: 'Use custom security groups?',
|
|
@@ -417,8 +485,8 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
417
485
|
let customSecurityGroups = undefined;
|
|
418
486
|
if (useCustomSecurityGroups) {
|
|
419
487
|
console.log();
|
|
420
|
-
console.log(chalk.yellow('
|
|
421
|
-
console.log(chalk.dim('
|
|
488
|
+
console.log(chalk.yellow(' Custom Security Groups'));
|
|
489
|
+
console.log(chalk.dim(' Provide the security group IDs from AWS Console.'));
|
|
422
490
|
console.log();
|
|
423
491
|
customSecurityGroups = {
|
|
424
492
|
workflowEngine: await input({
|
|
@@ -457,10 +525,11 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
457
525
|
}
|
|
458
526
|
// Custom IAM Roles
|
|
459
527
|
console.log();
|
|
460
|
-
console.log(chalk.cyan('
|
|
528
|
+
console.log(chalk.cyan(' ' + chalk.bold('Custom IAM Roles')));
|
|
461
529
|
console.log(chalk.dim(' IAM roles define permissions for AWS resources.'));
|
|
462
|
-
console.log(chalk.dim('
|
|
463
|
-
console.log(chalk.dim('
|
|
530
|
+
console.log(chalk.dim(' - ' + chalk.white('No') + ' - We\'ll create new IAM roles with proper permissions (recommended)'));
|
|
531
|
+
console.log(chalk.dim(' - ' + chalk.white('Yes') + ' - You provide existing IAM role ARNs (for advanced users)'));
|
|
532
|
+
ui.costSignal('Selecting Yes will ask for 10 IAM role ARNs');
|
|
464
533
|
console.log();
|
|
465
534
|
const useCustomIamPolicies = await confirm({
|
|
466
535
|
message: 'Use custom IAM roles?',
|
|
@@ -469,8 +538,8 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
469
538
|
let customIamRoles = undefined;
|
|
470
539
|
if (useCustomIamPolicies) {
|
|
471
540
|
console.log();
|
|
472
|
-
console.log(chalk.yellow('
|
|
473
|
-
console.log(chalk.dim('
|
|
541
|
+
console.log(chalk.yellow(' Custom IAM Roles'));
|
|
542
|
+
console.log(chalk.dim(' Provide the IAM role ARNs from AWS Console.'));
|
|
474
543
|
console.log();
|
|
475
544
|
const validateRoleArn = (v) => !v || /^arn:aws:iam::\d{12}:role\/.+$/.test(v) || 'Must be valid IAM role ARN';
|
|
476
545
|
customIamRoles = {
|
|
@@ -516,25 +585,38 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
516
585
|
}),
|
|
517
586
|
};
|
|
518
587
|
}
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
588
|
+
return {
|
|
589
|
+
workflowRunsKeyAlias: workflowRunsKeyAlias || undefined,
|
|
590
|
+
useCustomSecurityGroups,
|
|
591
|
+
customSecurityGroups,
|
|
592
|
+
useCustomIamPolicies,
|
|
593
|
+
customIamRoles,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
// ============================================================
|
|
597
|
+
// STEP: DNS & TLS CONFIGURATION
|
|
598
|
+
// ============================================================
|
|
599
|
+
async function promptTls(ctx, stepNumber, awsRegion) {
|
|
600
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'DNS & TLS Configuration');
|
|
601
|
+
console.log(chalk.dim(' Configure custom domains and SSL/TLS certificates for secure HTTPS access.'));
|
|
530
602
|
console.log();
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
603
|
+
const { partialConfig, mode } = ctx;
|
|
604
|
+
// Certificate hierarchy diagram (Advanced only — shows override options)
|
|
605
|
+
if (mode === 'advanced') {
|
|
606
|
+
console.log(chalk.dim(' Certificate Hierarchy:'));
|
|
607
|
+
console.log(chalk.dim(' Primary Cert (used for all components)'));
|
|
608
|
+
console.log(chalk.dim(' |-- CloudFront (can override, must be us-east-1)'));
|
|
609
|
+
console.log(chalk.dim(' |-- Public ALB (can override)'));
|
|
610
|
+
console.log(chalk.dim(' +-- Private ALB (can override)'));
|
|
611
|
+
console.log();
|
|
612
|
+
}
|
|
613
|
+
// ─── Primary Certificate ───
|
|
614
|
+
let certificateArn = await promptIfMissing(partialConfig?.tls?.certificateArn, async () => {
|
|
615
|
+
explainField('Primary Certificate (Optional)', 'An ACM certificate used for all TLS components (CloudFront, public ALB, private ALB).\n' +
|
|
616
|
+
(mode === 'advanced' ? 'Component-specific certificates can override this in the sections below.\n\n' : '\n') +
|
|
617
|
+
chalk.red(' IMPORTANT: ') + 'This certificate ' + chalk.bold('MUST') + ' be in the ' + chalk.bold('us-east-1') + ' region.\n' +
|
|
618
|
+
' (CloudFront requires us-east-1, so a single cert here must be in that region)\n\n' +
|
|
619
|
+
chalk.green(' Tip: ') + 'If you have a wildcard certificate (*.example.com), provide it here.', 'arn:aws:acm:us-east-1:123456789012:certificate/wildcard-123... (or leave blank)');
|
|
538
620
|
return input({
|
|
539
621
|
message: 'Primary Certificate ARN (must be in us-east-1, press Enter to skip):',
|
|
540
622
|
default: '',
|
|
@@ -550,16 +632,21 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
550
632
|
return true;
|
|
551
633
|
},
|
|
552
634
|
});
|
|
553
|
-
}, 'Primary Certificate ARN');
|
|
554
|
-
|
|
635
|
+
}, 'Primary Certificate ARN') || undefined;
|
|
636
|
+
const hasPrimaryCert = !!certificateArn;
|
|
637
|
+
// ─── CloudFront ───
|
|
638
|
+
ui.subsectionHeader('CloudFront');
|
|
639
|
+
// ─── CloudFront ───
|
|
640
|
+
ui.subsectionHeader('CloudFront');
|
|
555
641
|
const cloudfrontDomain = await promptIfMissing(partialConfig?.tls?.cloudfrontDomain, async () => {
|
|
556
|
-
explainField('CloudFront Domain', '
|
|
557
|
-
chalk.green('
|
|
642
|
+
explainField('CloudFront Domain (Optional)', 'A custom domain name for your Studio UI (the website users will visit).\nThis domain must match the SSL certificate you provide.\nLeave blank to use the default CloudFront domain (e.g. d1234abcdef.cloudfront.net).\n\n' +
|
|
643
|
+
chalk.green(' Example: ') + 'If your company is Acme Corp, you might use: studio.acme.com', 'studio.example.com or studio.acme.com (or leave blank for default CloudFront domain)');
|
|
558
644
|
return input({
|
|
559
|
-
message: 'CloudFront Domain:',
|
|
645
|
+
message: 'CloudFront Domain (press Enter to skip):',
|
|
646
|
+
default: '',
|
|
560
647
|
validate: (value) => {
|
|
561
648
|
if (!value)
|
|
562
|
-
return
|
|
649
|
+
return true;
|
|
563
650
|
if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/.test(value)) {
|
|
564
651
|
return 'Must be a valid domain name';
|
|
565
652
|
}
|
|
@@ -567,129 +654,171 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
567
654
|
},
|
|
568
655
|
});
|
|
569
656
|
}, 'CloudFront Domain');
|
|
570
|
-
|
|
571
|
-
//
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
explainField(hasPrimaryCert ? 'Public ALB Certificate (Optional)' : 'Public ALB Certificate', 'The ACM certificate ARN for the public-facing BFF API load balancer.' + (hasPrimaryCert ? '\nOverrides the primary certificate for the public ALB.' : '') + '\n\n' +
|
|
601
|
-
chalk.green('💡 Tip: ') + (hasPrimaryCert
|
|
602
|
-
? 'Leave blank to use the primary certificate for the public ALB.'
|
|
603
|
-
: 'This certificate should be in the same region as your infrastructure.'), 'arn:aws:acm:' + awsRegion + ':123456789012:certificate/def456...' + (hasPrimaryCert ? ' (or leave blank)' : ''));
|
|
604
|
-
return input({
|
|
605
|
-
message: 'Public ALB Certificate ARN' + (hasPrimaryCert ? ' (press Enter to skip):' : ':'),
|
|
606
|
-
default: hasPrimaryCert ? '' : undefined,
|
|
607
|
-
validate: (value) => {
|
|
608
|
-
if (!value) {
|
|
609
|
-
if (hasPrimaryCert)
|
|
657
|
+
// ─── Component certificate overrides (Advanced only) ───
|
|
658
|
+
// In express mode, the primary certificate is used for all components.
|
|
659
|
+
let cloudfrontCertArn;
|
|
660
|
+
let bffAlbCertificateArn;
|
|
661
|
+
if (mode === 'advanced') {
|
|
662
|
+
// CloudFront certificate override — only if a custom domain was provided
|
|
663
|
+
// (the default CloudFront domain uses its own built-in certificate)
|
|
664
|
+
if (cloudfrontDomain || partialConfig?.tls?.cloudfrontCertArn) {
|
|
665
|
+
cloudfrontCertArn = await promptIfMissing(partialConfig?.tls?.cloudfrontCertArn, async () => {
|
|
666
|
+
explainField(hasPrimaryCert ? 'CloudFront Certificate (Optional)' : 'CloudFront Certificate', 'The ACM certificate ARN for CloudFront.' + (hasPrimaryCert ? ' Overrides the primary certificate for CloudFront.' : '') + '\n\n' +
|
|
667
|
+
chalk.red(' IMPORTANT: ') + 'This certificate ' + chalk.bold('MUST') + ' be in the ' + chalk.bold('us-east-1') + ' region.\n' +
|
|
668
|
+
' (This is a CloudFront requirement, even if your infrastructure is in a different region)\n\n' +
|
|
669
|
+
chalk.green(' Tip: ') + (hasPrimaryCert
|
|
670
|
+
? 'Leave blank to use the primary certificate for CloudFront.'
|
|
671
|
+
: 'Create the certificate in ACM in us-east-1, then copy the ARN here.'), 'arn:aws:acm:us-east-1:123456789012:certificate/abc123...' + (hasPrimaryCert ? ' (or leave blank)' : ''));
|
|
672
|
+
return input({
|
|
673
|
+
message: 'CloudFront Certificate ARN (must be in us-east-1' + (hasPrimaryCert ? ', press Enter to skip' : '') + '):',
|
|
674
|
+
default: hasPrimaryCert ? '' : undefined,
|
|
675
|
+
validate: (value) => {
|
|
676
|
+
if (!value) {
|
|
677
|
+
if (hasPrimaryCert)
|
|
678
|
+
return true;
|
|
679
|
+
return 'CloudFront certificate ARN is required (no primary certificate provided)';
|
|
680
|
+
}
|
|
681
|
+
const result = acmCertArnSchema.safeParse(value);
|
|
682
|
+
if (!result.success)
|
|
683
|
+
return 'Must be a valid ACM certificate ARN';
|
|
684
|
+
if (!value.includes('us-east-1')) {
|
|
685
|
+
return chalk.red('ERROR: ') + 'CloudFront certificate MUST be in us-east-1 region (CloudFront requirement)';
|
|
686
|
+
}
|
|
610
687
|
return true;
|
|
611
|
-
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
688
|
+
},
|
|
689
|
+
});
|
|
690
|
+
}, 'CloudFront Certificate ARN');
|
|
691
|
+
}
|
|
692
|
+
// Public ALB certificate override
|
|
693
|
+
ui.subsectionHeader('Public ALB');
|
|
694
|
+
bffAlbCertificateArn = await promptIfMissing(partialConfig?.tls?.bffAlbCertificateArn, async () => {
|
|
695
|
+
explainField(hasPrimaryCert ? 'Public ALB Certificate (Optional)' : 'Public ALB Certificate', 'The ACM certificate ARN for the public-facing BFF API load balancer.' + (hasPrimaryCert ? '\nOverrides the primary certificate for the public ALB.' : '') + '\n\n' +
|
|
696
|
+
chalk.green(' Tip: ') + (hasPrimaryCert
|
|
697
|
+
? 'Leave blank to use the primary certificate for the public ALB.'
|
|
698
|
+
: 'This certificate should be in the same region as your infrastructure.'), 'arn:aws:acm:' + awsRegion + ':123456789012:certificate/def456...' + (hasPrimaryCert ? ' (or leave blank)' : ''));
|
|
699
|
+
return input({
|
|
700
|
+
message: 'Public ALB Certificate ARN' + (hasPrimaryCert ? ' (press Enter to skip):' : ':'),
|
|
701
|
+
default: hasPrimaryCert ? '' : undefined,
|
|
702
|
+
validate: (value) => {
|
|
703
|
+
if (!value) {
|
|
704
|
+
if (hasPrimaryCert)
|
|
705
|
+
return true;
|
|
706
|
+
return 'Public ALB certificate ARN is required (no primary certificate provided)';
|
|
707
|
+
}
|
|
708
|
+
const result = acmCertArnSchema.safeParse(value);
|
|
709
|
+
if (!result.success)
|
|
710
|
+
return 'Must be a valid ACM certificate ARN';
|
|
711
|
+
return true;
|
|
712
|
+
},
|
|
633
713
|
});
|
|
634
|
-
})
|
|
714
|
+
}, 'Public ALB Certificate ARN');
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
// Express: use config overrides if present, otherwise primary cert covers everything
|
|
718
|
+
cloudfrontCertArn = partialConfig?.tls?.cloudfrontCertArn;
|
|
719
|
+
bffAlbCertificateArn = partialConfig?.tls?.bffAlbCertificateArn;
|
|
635
720
|
}
|
|
636
|
-
// BFF ALB Ingress
|
|
721
|
+
// BFF ALB Internal + Ingress CIDRs (Advanced only)
|
|
722
|
+
let bffAlbInternal;
|
|
637
723
|
let bffAlbIngressCidrBlocks;
|
|
638
|
-
if (
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
724
|
+
if (mode === 'advanced') {
|
|
725
|
+
const hasBffAlbCert = !!(bffAlbCertificateArn || certificateArn);
|
|
726
|
+
if (hasBffAlbCert) {
|
|
727
|
+
bffAlbInternal = partialConfig?.tls?.bffAlbInternal ?? await (async () => {
|
|
728
|
+
console.log();
|
|
729
|
+
console.log(chalk.cyan(' ' + chalk.bold('Force BFF ALB Internal')));
|
|
730
|
+
console.log(chalk.dim(' When enabled, the BFF ALB will be internal even though a certificate is provided.'));
|
|
731
|
+
console.log(chalk.dim(' Use this for private DNS with ACM Private CA configurations.'));
|
|
732
|
+
console.log();
|
|
733
|
+
return confirm({
|
|
734
|
+
message: 'Force BFF ALB to be internal? (for private DNS with ACM Private CA)',
|
|
735
|
+
default: false,
|
|
736
|
+
});
|
|
737
|
+
})();
|
|
643
738
|
}
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
739
|
+
if (bffAlbInternal) {
|
|
740
|
+
if (partialConfig?.tls?.bffAlbIngressCidrBlocks?.length) {
|
|
741
|
+
bffAlbIngressCidrBlocks = partialConfig.tls.bffAlbIngressCidrBlocks;
|
|
742
|
+
ui.info(`Using BFF ALB Ingress CIDRs: ${ui.highlight(bffAlbIngressCidrBlocks.join(', '))} (from config)`);
|
|
743
|
+
console.log();
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
explainField('BFF ALB Ingress CIDR Blocks (Optional)', 'Restrict which CIDR blocks can reach the internal BFF ALB.\nLeave blank to fall back to VPC CIDR (Terraform default).', '10.0.0.0/8,172.16.0.0/12');
|
|
747
|
+
const cidrInput = await input({
|
|
748
|
+
message: 'CIDR blocks for internal BFF ALB ingress (comma-separated, press Enter to skip):',
|
|
749
|
+
default: '',
|
|
750
|
+
validate: (value) => {
|
|
751
|
+
if (!value)
|
|
752
|
+
return true;
|
|
753
|
+
const cidrs = value.split(',').map(c => c.trim());
|
|
754
|
+
const cidrRegex = /^\d+\.\d+\.\d+\.\d+\/\d+$/;
|
|
755
|
+
for (const cidr of cidrs) {
|
|
756
|
+
if (!cidrRegex.test(cidr)) {
|
|
757
|
+
return `Invalid CIDR block: ${cidr}`;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
return true;
|
|
761
|
+
},
|
|
762
|
+
});
|
|
763
|
+
if (cidrInput) {
|
|
764
|
+
bffAlbIngressCidrBlocks = cidrInput.split(',').map(c => c.trim());
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
// ─── Private Services ALB ─── (Advanced only)
|
|
770
|
+
let bffInternalAlbCertificateArn;
|
|
771
|
+
let privateCaCertSecretArn;
|
|
772
|
+
if (mode === 'advanced') {
|
|
773
|
+
ui.subsectionHeader('Private Services ALB');
|
|
774
|
+
bffInternalAlbCertificateArn = await promptIfMissing(partialConfig?.tls?.bffInternalAlbCertificateArn, async () => {
|
|
775
|
+
explainField('Private Services ALB Certificate (Optional)', 'The ACM certificate ARN for the private services load balancer.\nThis ALB handles internal service-to-service traffic (e.g. workspace runtime, websockets).\nOverrides the primary certificate for this ALB.\n\n' +
|
|
776
|
+
chalk.green(' Tip: ') + 'Leave blank to use the primary certificate.', 'arn:aws:acm:' + awsRegion + ':123456789012:certificate/ghi789... (or leave blank)');
|
|
777
|
+
return input({
|
|
778
|
+
message: 'Private Services ALB Certificate ARN (press Enter to skip):',
|
|
648
779
|
default: '',
|
|
649
780
|
validate: (value) => {
|
|
650
781
|
if (!value)
|
|
651
782
|
return true;
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
if (!cidrRegex.test(cidr)) {
|
|
656
|
-
return `Invalid CIDR block: ${cidr}`;
|
|
657
|
-
}
|
|
658
|
-
}
|
|
783
|
+
const result = acmCertArnSchema.safeParse(value);
|
|
784
|
+
if (!result.success)
|
|
785
|
+
return 'Must be a valid ACM certificate ARN';
|
|
659
786
|
return true;
|
|
660
787
|
},
|
|
661
788
|
});
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
789
|
+
}, 'Private Services ALB Certificate ARN') || undefined;
|
|
790
|
+
privateCaCertSecretArn = await promptIfMissing(partialConfig?.tls?.privateCaCertSecretArn, async () => {
|
|
791
|
+
explainField('Private CA Certificate Secret (Optional)', 'If either load balancer uses an ACM Private CA certificate, services need the CA\nroot certificate to trust it. Provide the Secrets Manager ARN that holds the PEM file.', 'arn:aws:secretsmanager:' + awsRegion + ':123456789012:secret:my-private-ca-pem-AbCdEf');
|
|
792
|
+
return input({
|
|
793
|
+
message: 'Private CA Cert Secret ARN (press Enter to skip):',
|
|
794
|
+
default: '',
|
|
795
|
+
validate: (value) => {
|
|
796
|
+
if (!value)
|
|
797
|
+
return true;
|
|
798
|
+
const result = secretsManagerArnSchema.safeParse(value);
|
|
799
|
+
if (!result.success)
|
|
800
|
+
return 'Must be a valid AWS Secrets Manager ARN';
|
|
801
|
+
return true;
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
}, 'Private CA Cert Secret ARN') || undefined;
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// Express: use config values if present
|
|
808
|
+
bffInternalAlbCertificateArn = partialConfig?.tls?.bffInternalAlbCertificateArn;
|
|
809
|
+
privateCaCertSecretArn = partialConfig?.tls?.privateCaCertSecretArn;
|
|
666
810
|
}
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
811
|
+
// ─── DNS ───
|
|
812
|
+
ui.subsectionHeader('DNS');
|
|
813
|
+
const hostedZoneId = await promptIfMissing(partialConfig?.tls?.hostedZoneId, async () => {
|
|
814
|
+
explainField('Route53 Hosted Zone ID (Optional)', 'The ID of your Route53 hosted zone where DNS records will be automatically created.\nYou can find this in the AWS Console under Route53 > Hosted zones.\nLeave blank if not using public DNS (e.g. internal ALB with ACM Private CA).\n\n' +
|
|
815
|
+
chalk.green(' Tip: ') + 'The hosted zone should manage the domain you\'re using (e.g., example.com).', 'Z1234567890ABC (or leave blank)');
|
|
671
816
|
return input({
|
|
672
|
-
message: '
|
|
817
|
+
message: 'Route53 Hosted Zone ID (press Enter to skip):',
|
|
673
818
|
default: '',
|
|
674
819
|
validate: (value) => {
|
|
675
820
|
if (!value)
|
|
676
821
|
return true;
|
|
677
|
-
const result = acmCertArnSchema.safeParse(value);
|
|
678
|
-
if (!result.success)
|
|
679
|
-
return 'Must be a valid ACM certificate ARN';
|
|
680
|
-
return true;
|
|
681
|
-
},
|
|
682
|
-
});
|
|
683
|
-
}, 'Internal ALB Certificate ARN');
|
|
684
|
-
// Hosted Zone ID
|
|
685
|
-
const hostedZoneId = await promptIfMissing(partialConfig?.tls?.hostedZoneId, async () => {
|
|
686
|
-
explainField('Route53 Hosted Zone ID', 'The ID of your Route53 hosted zone where DNS records will be automatically created.\nYou can find this in the AWS Console under Route53 → Hosted zones.\n\n' +
|
|
687
|
-
chalk.green('💡 Tip: ') + 'The hosted zone should manage the domain you\'re using (e.g., example.com).', 'Z1234567890ABC');
|
|
688
|
-
return input({
|
|
689
|
-
message: 'Route53 Hosted Zone ID:',
|
|
690
|
-
validate: (value) => {
|
|
691
|
-
if (!value)
|
|
692
|
-
return 'Hosted Zone ID is required';
|
|
693
822
|
const result = route53ZoneIdSchema.safeParse(value);
|
|
694
823
|
if (!result.success)
|
|
695
824
|
return 'Must be a valid Route53 hosted zone ID (starts with Z)';
|
|
@@ -697,37 +826,52 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
697
826
|
},
|
|
698
827
|
});
|
|
699
828
|
}, 'Route53 Hosted Zone ID');
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
829
|
+
return {
|
|
830
|
+
certificateArn: certificateArn || undefined,
|
|
831
|
+
cloudfrontDomain: cloudfrontDomain || undefined,
|
|
832
|
+
cloudfrontCertArn: cloudfrontCertArn || undefined,
|
|
833
|
+
bffAlbCertificateArn: bffAlbCertificateArn || undefined,
|
|
834
|
+
bffAlbInternal: bffAlbInternal || undefined,
|
|
835
|
+
bffAlbIngressCidrBlocks: bffAlbIngressCidrBlocks?.length ? bffAlbIngressCidrBlocks : undefined,
|
|
836
|
+
bffInternalAlbCertificateArn: bffInternalAlbCertificateArn || undefined,
|
|
837
|
+
privateCaCertSecretArn: privateCaCertSecretArn || undefined,
|
|
838
|
+
hostedZoneId: hostedZoneId || undefined,
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
// ============================================================
|
|
842
|
+
// STEP: FEATURE CONFIGURATION
|
|
843
|
+
// ============================================================
|
|
844
|
+
async function promptFeatures(ctx, stepNumber) {
|
|
845
|
+
// Express mode: auto-enable WAF
|
|
846
|
+
if (ctx.mode === 'express') {
|
|
847
|
+
ui.info('Using default Features: WAF enabled');
|
|
848
|
+
console.log();
|
|
849
|
+
return { enableBffWaf: true };
|
|
850
|
+
}
|
|
851
|
+
// Advanced mode: prompt
|
|
852
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Feature Configuration');
|
|
853
|
+
console.log(chalk.dim(' Enable or disable optional Sembix Studio services.'));
|
|
854
|
+
console.log(chalk.dim(' Configure web application firewall settings.'));
|
|
706
855
|
console.log();
|
|
707
|
-
// Sembix Studio Notifications
|
|
708
|
-
explainField('Sembix Studio Notifications Service', 'The Notifications service handles alerts, messaging, and event notifications.\n\n' +
|
|
709
|
-
chalk.green('✓ Recommended: ') + 'Enable this feature for user notifications and alerts.', 'Choose Yes (recommended)');
|
|
710
|
-
const deploySembixStudioNotifications = await confirm({
|
|
711
|
-
message: 'Deploy Sembix Studio Notifications service?',
|
|
712
|
-
default: true,
|
|
713
|
-
});
|
|
714
|
-
// WAF
|
|
715
856
|
explainField('Web Application Firewall (WAF)', 'AWS WAF protects your BFF API from common web exploits and attacks.\nIt provides an additional layer of security at the application level.\n\n' +
|
|
716
|
-
chalk.green('
|
|
857
|
+
chalk.green(' Recommended: ') + 'Enable this for production environments.', 'Choose Yes (recommended for production)');
|
|
717
858
|
const enableBffWaf = await confirm({
|
|
718
859
|
message: 'Enable Web Application Firewall (WAF) for BFF?',
|
|
719
860
|
default: true,
|
|
720
861
|
});
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
862
|
+
return { enableBffWaf };
|
|
863
|
+
}
|
|
864
|
+
// ============================================================
|
|
865
|
+
// STEP: FRONTEND CONFIGURATION
|
|
866
|
+
// ============================================================
|
|
867
|
+
async function promptFrontend(ctx, stepNumber) {
|
|
868
|
+
ui.stepHeader(stepNumber, getStepCount(ctx.mode), 'Frontend Configuration');
|
|
869
|
+
console.log(chalk.dim(' Configure integrations for the Sembix Studio frontend (React UI).'));
|
|
726
870
|
console.log();
|
|
727
|
-
|
|
871
|
+
const { partialConfig } = ctx;
|
|
728
872
|
const githubAppClientId = await promptIfMissing(partialConfig?.frontend?.githubAppClientId, async () => {
|
|
729
873
|
explainField('GitHub App Client ID', 'The OAuth Client ID from your GitHub App.\nThis allows users to authenticate with GitHub and access repositories.\n\n' +
|
|
730
|
-
chalk.green('
|
|
874
|
+
chalk.green(' Tip: ') + 'Create a GitHub App in your organization settings, then copy the Client ID.', 'Iv1.0123456789abcdef');
|
|
731
875
|
return input({
|
|
732
876
|
message: 'GitHub App Client ID:',
|
|
733
877
|
validate: (value) => {
|
|
@@ -737,7 +881,6 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
737
881
|
},
|
|
738
882
|
});
|
|
739
883
|
}, 'GitHub App Client ID');
|
|
740
|
-
// GitHub App Name
|
|
741
884
|
const githubAppName = await promptIfMissing(partialConfig?.frontend?.githubAppName, async () => {
|
|
742
885
|
explainField('GitHub App Name', 'The name of your GitHub App (as it appears in GitHub).\nThis is shown to users during the OAuth flow.', 'sembix-studio-app or acme-studio');
|
|
743
886
|
return input({
|
|
@@ -749,10 +892,9 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
749
892
|
},
|
|
750
893
|
});
|
|
751
894
|
}, 'GitHub App Name');
|
|
752
|
-
// Jira Client ID
|
|
753
895
|
const jiraClientId = await promptIfMissing(partialConfig?.frontend?.jiraClientId, async () => {
|
|
754
896
|
explainField('Jira Client ID', 'The OAuth Client ID from your Atlassian/Jira app.\nThis enables Jira integration for issue tracking and project management.\n\n' +
|
|
755
|
-
chalk.green('
|
|
897
|
+
chalk.green(' Tip: ') + 'Create an OAuth app in your Atlassian Developer Console.', 'abc123def456ghi789');
|
|
756
898
|
return input({
|
|
757
899
|
message: 'Jira Client ID:',
|
|
758
900
|
validate: (value) => {
|
|
@@ -762,44 +904,103 @@ export async function promptEnvironmentSetup(githubClient, providedEnvironmentNa
|
|
|
762
904
|
},
|
|
763
905
|
});
|
|
764
906
|
}, 'Jira Client ID');
|
|
907
|
+
return { githubAppClientId, githubAppName, jiraClientId };
|
|
908
|
+
}
|
|
909
|
+
// ============================================================
|
|
910
|
+
// ORCHESTRATOR
|
|
911
|
+
// ============================================================
|
|
912
|
+
export async function promptEnvironmentSetup(githubClient, providedEnvironmentName, providedRepo, partialConfig, isUpdate = false) {
|
|
913
|
+
// We need an initial mode to set up step numbering for the first step.
|
|
914
|
+
// Mode selection happens after repo, so use a preliminary context.
|
|
915
|
+
const preliminaryCtx = {
|
|
916
|
+
mode: 'express', // temporary — will be updated after mode selection
|
|
917
|
+
githubClient,
|
|
918
|
+
partialConfig,
|
|
919
|
+
isUpdate,
|
|
920
|
+
};
|
|
921
|
+
// Step 1: Repository Selection (always step 1)
|
|
922
|
+
const repository = await promptRepository(preliminaryCtx, 1, providedRepo);
|
|
923
|
+
// Mode Selection (between repo and basic config)
|
|
924
|
+
const mode = await promptModeSelection(partialConfig);
|
|
925
|
+
const ctx = { mode, githubClient, partialConfig, isUpdate };
|
|
926
|
+
// Step 2: Basic Configuration
|
|
927
|
+
const basicConfig = await promptBasicConfig(ctx, 2, repository.owner, repository.repo, providedEnvironmentName);
|
|
928
|
+
// Steps 3-5: Database, Networking, Security (Advanced only as visible steps)
|
|
929
|
+
let database;
|
|
930
|
+
let networking;
|
|
931
|
+
let security;
|
|
932
|
+
if (mode === 'advanced') {
|
|
933
|
+
database = await promptDatabase(ctx, 3);
|
|
934
|
+
networking = await promptNetworking(ctx, 4);
|
|
935
|
+
// Checkpoint: Infrastructure
|
|
936
|
+
ui.checkpoint('Infrastructure Basics', {
|
|
937
|
+
'Environment': basicConfig.environmentName,
|
|
938
|
+
'AWS Account': basicConfig.awsAccountId,
|
|
939
|
+
'Region': basicConfig.awsRegion,
|
|
940
|
+
'Database': database.name,
|
|
941
|
+
'Networking': networking.useCustomNetworking ? `Custom VPC (${networking.customVpcId})` : 'New VPC (default)',
|
|
942
|
+
});
|
|
943
|
+
security = await promptSecurity(ctx, 5);
|
|
944
|
+
}
|
|
945
|
+
else {
|
|
946
|
+
// Express mode: auto-default DB, Networking, Security
|
|
947
|
+
database = await promptDatabase(ctx, 0); // step number unused in express
|
|
948
|
+
networking = await promptNetworking(ctx, 0);
|
|
949
|
+
security = await promptSecurity(ctx, 0);
|
|
950
|
+
}
|
|
951
|
+
// TLS step
|
|
952
|
+
const tlsStepNumber = mode === 'advanced' ? 6 : 3;
|
|
953
|
+
const tls = await promptTls(ctx, tlsStepNumber, basicConfig.awsRegion);
|
|
954
|
+
// Checkpoint: TLS & DNS
|
|
955
|
+
const tlsCheckpointData = {};
|
|
956
|
+
if (tls.cloudfrontDomain)
|
|
957
|
+
tlsCheckpointData['CloudFront Domain'] = tls.cloudfrontDomain;
|
|
958
|
+
if (tls.cloudfrontCertArn)
|
|
959
|
+
tlsCheckpointData['CloudFront Cert'] = tls.cloudfrontCertArn;
|
|
960
|
+
if (tls.bffAlbCertificateArn) {
|
|
961
|
+
tlsCheckpointData['Public ALB Cert'] = tls.bffAlbCertificateArn;
|
|
962
|
+
}
|
|
963
|
+
else if (tls.certificateArn) {
|
|
964
|
+
tlsCheckpointData['Public ALB Cert'] = '(using primary)';
|
|
965
|
+
}
|
|
966
|
+
if (tls.hostedZoneId)
|
|
967
|
+
tlsCheckpointData['Hosted Zone'] = tls.hostedZoneId;
|
|
968
|
+
if (Object.keys(tlsCheckpointData).length > 0) {
|
|
969
|
+
ui.checkpoint('TLS & DNS', tlsCheckpointData);
|
|
970
|
+
}
|
|
971
|
+
// Features step (Advanced only as visible step)
|
|
972
|
+
let features;
|
|
973
|
+
if (mode === 'advanced') {
|
|
974
|
+
features = await promptFeatures(ctx, 7);
|
|
975
|
+
}
|
|
976
|
+
else {
|
|
977
|
+
features = await promptFeatures(ctx, 0);
|
|
978
|
+
}
|
|
979
|
+
// Frontend step
|
|
980
|
+
const frontendStepNumber = mode === 'advanced' ? 8 : 4;
|
|
981
|
+
const frontend = await promptFrontend(ctx, frontendStepNumber);
|
|
765
982
|
return {
|
|
766
|
-
repository: { owner, repo },
|
|
767
|
-
environmentName,
|
|
768
|
-
awsAccountId,
|
|
769
|
-
awsRegion,
|
|
770
|
-
customerRoleArn,
|
|
771
|
-
terraformStateBucket,
|
|
983
|
+
repository: { owner: repository.owner, repo: repository.repo },
|
|
984
|
+
environmentName: basicConfig.environmentName,
|
|
985
|
+
awsAccountId: basicConfig.awsAccountId,
|
|
986
|
+
awsRegion: basicConfig.awsRegion,
|
|
987
|
+
customerRoleArn: basicConfig.customerRoleArn,
|
|
988
|
+
terraformStateBucket: basicConfig.terraformStateBucket,
|
|
772
989
|
database: {
|
|
773
|
-
name:
|
|
774
|
-
user:
|
|
990
|
+
name: database.name,
|
|
991
|
+
user: database.user,
|
|
775
992
|
},
|
|
776
993
|
networking,
|
|
777
994
|
security: {
|
|
778
|
-
workflowRunsKeyAlias: workflowRunsKeyAlias
|
|
779
|
-
useCustomSecurityGroups,
|
|
780
|
-
customSecurityGroups,
|
|
781
|
-
useCustomIamPolicies,
|
|
782
|
-
customIamRoles,
|
|
783
|
-
},
|
|
784
|
-
tls: {
|
|
785
|
-
certificateArn: certificateArn || undefined,
|
|
786
|
-
cloudfrontDomain,
|
|
787
|
-
cloudfrontCertArn: cloudfrontCertArn || undefined,
|
|
788
|
-
bffAlbCertificateArn: bffAlbCertificateArn || undefined,
|
|
789
|
-
bffAlbInternal: bffAlbInternal || undefined,
|
|
790
|
-
bffAlbIngressCidrBlocks: bffAlbIngressCidrBlocks?.length ? bffAlbIngressCidrBlocks : undefined,
|
|
791
|
-
bffInternalAlbCertificateArn: bffInternalAlbCertificateArn || undefined,
|
|
792
|
-
hostedZoneId,
|
|
793
|
-
},
|
|
794
|
-
features: {
|
|
795
|
-
deploySembixStudioNotifications,
|
|
796
|
-
enableBffWaf,
|
|
797
|
-
},
|
|
798
|
-
frontend: {
|
|
799
|
-
githubAppClientId,
|
|
800
|
-
githubAppName,
|
|
801
|
-
jiraClientId,
|
|
995
|
+
workflowRunsKeyAlias: security.workflowRunsKeyAlias,
|
|
996
|
+
useCustomSecurityGroups: security.useCustomSecurityGroups,
|
|
997
|
+
customSecurityGroups: security.customSecurityGroups,
|
|
998
|
+
useCustomIamPolicies: security.useCustomIamPolicies,
|
|
999
|
+
customIamRoles: security.customIamRoles,
|
|
802
1000
|
},
|
|
1001
|
+
tls,
|
|
1002
|
+
features,
|
|
1003
|
+
frontend,
|
|
803
1004
|
};
|
|
804
1005
|
}
|
|
805
1006
|
//# sourceMappingURL=environment-setup.js.map
|