@quiltdata/benchling-webhook 0.5.4 → 0.6.1-20251104T043302Z

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.
Files changed (107) hide show
  1. package/README.md +295 -12
  2. package/dist/bin/benchling-webhook.d.ts +1 -1
  3. package/dist/bin/benchling-webhook.d.ts.map +1 -1
  4. package/dist/bin/benchling-webhook.js +12 -22
  5. package/dist/bin/benchling-webhook.js.map +1 -1
  6. package/dist/bin/cdk-dev.js +59 -3
  7. package/dist/bin/cli.js +27 -9
  8. package/dist/bin/cli.js.map +1 -1
  9. package/dist/bin/commands/deploy.d.ts +6 -2
  10. package/dist/bin/commands/deploy.d.ts.map +1 -1
  11. package/dist/bin/commands/deploy.js +151 -90
  12. package/dist/bin/commands/deploy.js.map +1 -1
  13. package/dist/bin/commands/setup-wizard.d.ts +22 -0
  14. package/dist/bin/commands/setup-wizard.d.ts.map +1 -0
  15. package/dist/bin/commands/setup-wizard.js +47 -0
  16. package/dist/bin/commands/setup-wizard.js.map +1 -0
  17. package/dist/bin/config-profiles.d.ts +59 -0
  18. package/dist/bin/config-profiles.d.ts.map +1 -0
  19. package/dist/bin/config-profiles.js +272 -0
  20. package/dist/bin/config-profiles.js.map +1 -0
  21. package/dist/bin/create-secret.d.ts +25 -0
  22. package/dist/bin/create-secret.d.ts.map +1 -0
  23. package/dist/bin/create-secret.js +239 -0
  24. package/dist/bin/create-secret.js.map +1 -0
  25. package/dist/lib/benchling-auth-validator.d.ts +65 -0
  26. package/dist/lib/benchling-auth-validator.d.ts.map +1 -0
  27. package/dist/lib/benchling-auth-validator.js +213 -0
  28. package/dist/lib/benchling-auth-validator.js.map +1 -0
  29. package/dist/lib/benchling-webhook-stack.d.ts +13 -10
  30. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  31. package/dist/lib/benchling-webhook-stack.js +25 -69
  32. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  33. package/dist/lib/config-logger.d.ts +191 -0
  34. package/dist/lib/config-logger.d.ts.map +1 -0
  35. package/dist/lib/config-logger.js +372 -0
  36. package/dist/lib/config-logger.js.map +1 -0
  37. package/dist/lib/configuration-saver.d.ts +75 -0
  38. package/dist/lib/configuration-saver.d.ts.map +1 -0
  39. package/dist/lib/configuration-saver.js +145 -0
  40. package/dist/lib/configuration-saver.js.map +1 -0
  41. package/dist/lib/configuration-validator.d.ts +63 -0
  42. package/dist/lib/configuration-validator.d.ts.map +1 -0
  43. package/dist/lib/configuration-validator.js +136 -0
  44. package/dist/lib/configuration-validator.js.map +1 -0
  45. package/dist/lib/configuration-wizard.d.ts +52 -0
  46. package/dist/lib/configuration-wizard.d.ts.map +1 -0
  47. package/dist/lib/configuration-wizard.js +193 -0
  48. package/dist/lib/configuration-wizard.js.map +1 -0
  49. package/dist/lib/fargate-service.d.ts +18 -9
  50. package/dist/lib/fargate-service.d.ts.map +1 -1
  51. package/dist/lib/fargate-service.js +177 -61
  52. package/dist/lib/fargate-service.js.map +1 -1
  53. package/dist/lib/quilt-config-resolver.d.ts +53 -0
  54. package/dist/lib/quilt-config-resolver.d.ts.map +1 -0
  55. package/dist/lib/quilt-config-resolver.js +100 -0
  56. package/dist/lib/quilt-config-resolver.js.map +1 -0
  57. package/dist/lib/s3-bucket-validator.d.ts +76 -0
  58. package/dist/lib/s3-bucket-validator.d.ts.map +1 -0
  59. package/dist/lib/s3-bucket-validator.js +237 -0
  60. package/dist/lib/s3-bucket-validator.js.map +1 -0
  61. package/dist/lib/types/config.d.ts +398 -0
  62. package/dist/lib/types/config.d.ts.map +1 -0
  63. package/dist/lib/types/config.js +11 -0
  64. package/dist/lib/types/config.js.map +1 -0
  65. package/dist/lib/utils/config-loader.d.ts +48 -0
  66. package/dist/lib/utils/config-loader.d.ts.map +1 -0
  67. package/dist/lib/utils/config-loader.js +109 -0
  68. package/dist/lib/utils/config-loader.js.map +1 -0
  69. package/dist/lib/utils/config-resolver.d.ts +138 -0
  70. package/dist/lib/utils/config-resolver.d.ts.map +1 -0
  71. package/dist/lib/utils/config-resolver.js +272 -0
  72. package/dist/lib/utils/config-resolver.js.map +1 -0
  73. package/dist/lib/utils/config.d.ts +50 -0
  74. package/dist/lib/utils/config.d.ts.map +1 -1
  75. package/dist/lib/utils/config.js +86 -0
  76. package/dist/lib/utils/config.js.map +1 -1
  77. package/dist/lib/utils/secrets.d.ts +174 -0
  78. package/dist/lib/utils/secrets.d.ts.map +1 -0
  79. package/dist/lib/utils/secrets.js +351 -0
  80. package/dist/lib/utils/secrets.js.map +1 -0
  81. package/dist/lib/xdg-cli-wrapper.d.ts +113 -0
  82. package/dist/lib/xdg-cli-wrapper.d.ts.map +1 -0
  83. package/dist/lib/xdg-cli-wrapper.js +288 -0
  84. package/dist/lib/xdg-cli-wrapper.js.map +1 -0
  85. package/dist/lib/xdg-config.d.ts +187 -0
  86. package/dist/lib/xdg-config.d.ts.map +1 -0
  87. package/dist/lib/xdg-config.js +562 -0
  88. package/dist/lib/xdg-config.js.map +1 -0
  89. package/dist/package.json +34 -26
  90. package/dist/scripts/config-health-check.d.ts +78 -0
  91. package/dist/scripts/config-health-check.d.ts.map +1 -0
  92. package/dist/scripts/config-health-check.js +559 -0
  93. package/dist/scripts/config-health-check.js.map +1 -0
  94. package/dist/scripts/infer-quilt-config.d.ts +50 -0
  95. package/dist/scripts/infer-quilt-config.d.ts.map +1 -0
  96. package/dist/scripts/infer-quilt-config.js +353 -0
  97. package/dist/scripts/infer-quilt-config.js.map +1 -0
  98. package/dist/scripts/install-wizard.d.ts +34 -0
  99. package/dist/scripts/install-wizard.d.ts.map +1 -0
  100. package/dist/scripts/install-wizard.js +719 -0
  101. package/dist/scripts/install-wizard.js.map +1 -0
  102. package/dist/scripts/sync-secrets.d.ts +63 -0
  103. package/dist/scripts/sync-secrets.d.ts.map +1 -0
  104. package/dist/scripts/sync-secrets.js +424 -0
  105. package/dist/scripts/sync-secrets.js.map +1 -0
  106. package/env.template +60 -47
  107. package/package.json +34 -26
@@ -0,0 +1,719 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * Interactive Configuration Wizard
5
+ *
6
+ * Guided configuration setup with comprehensive validation:
7
+ * - Benchling tenant and OAuth credentials
8
+ * - S3 bucket access verification
9
+ * - Quilt API connectivity testing
10
+ * - AWS Secrets Manager integration
11
+ *
12
+ * Supports both interactive and non-interactive (CI/CD) modes.
13
+ *
14
+ * @module scripts/install-wizard
15
+ */
16
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
17
+ if (k2 === undefined) k2 = k;
18
+ var desc = Object.getOwnPropertyDescriptor(m, k);
19
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
20
+ desc = { enumerable: true, get: function() { return m[k]; } };
21
+ }
22
+ Object.defineProperty(o, k2, desc);
23
+ }) : (function(o, m, k, k2) {
24
+ if (k2 === undefined) k2 = k;
25
+ o[k2] = m[k];
26
+ }));
27
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
28
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
29
+ }) : function(o, v) {
30
+ o["default"] = v;
31
+ });
32
+ var __importStar = (this && this.__importStar) || (function () {
33
+ var ownKeys = function(o) {
34
+ ownKeys = Object.getOwnPropertyNames || function (o) {
35
+ var ar = [];
36
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
37
+ return ar;
38
+ };
39
+ return ownKeys(o);
40
+ };
41
+ return function (mod) {
42
+ if (mod && mod.__esModule) return mod;
43
+ var result = {};
44
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
45
+ __setModuleDefault(result, mod);
46
+ return result;
47
+ };
48
+ })();
49
+ var __importDefault = (this && this.__importDefault) || function (mod) {
50
+ return (mod && mod.__esModule) ? mod : { "default": mod };
51
+ };
52
+ Object.defineProperty(exports, "__esModule", { value: true });
53
+ exports.runInstallWizard = runInstallWizard;
54
+ const inquirer_1 = __importDefault(require("inquirer"));
55
+ const client_s3_1 = require("@aws-sdk/client-s3");
56
+ const client_sts_1 = require("@aws-sdk/client-sts");
57
+ const xdg_config_1 = require("../lib/xdg-config");
58
+ const infer_quilt_config_1 = require("./infer-quilt-config");
59
+ const sync_secrets_1 = require("./sync-secrets");
60
+ const config_resolver_1 = require("../lib/utils/config-resolver");
61
+ const client_cloudformation_1 = require("@aws-sdk/client-cloudformation");
62
+ const https = __importStar(require("https"));
63
+ /**
64
+ * Validates Benchling tenant accessibility
65
+ *
66
+ * @param tenant - Benchling tenant name
67
+ * @returns Validation result
68
+ */
69
+ async function validateBenchlingTenant(tenant) {
70
+ const result = {
71
+ isValid: false,
72
+ errors: [],
73
+ warnings: [],
74
+ };
75
+ if (!tenant || tenant.trim().length === 0) {
76
+ result.errors.push("Tenant name cannot be empty");
77
+ return result;
78
+ }
79
+ // Basic format validation
80
+ if (!/^[a-zA-Z0-9-_]+$/.test(tenant)) {
81
+ result.errors.push("Tenant name contains invalid characters");
82
+ return result;
83
+ }
84
+ // Test tenant URL accessibility
85
+ const tenantUrl = `https://${tenant}.benchling.com`;
86
+ return new Promise((resolve) => {
87
+ https
88
+ .get(tenantUrl, { timeout: 5000 }, (res) => {
89
+ if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
90
+ result.isValid = true;
91
+ console.log(` ✓ Tenant URL accessible: ${tenantUrl}`);
92
+ }
93
+ else {
94
+ result.warnings.push(`Tenant URL returned status ${res.statusCode}`);
95
+ result.isValid = true; // Consider this a warning, not an error
96
+ }
97
+ resolve(result);
98
+ })
99
+ .on("error", (error) => {
100
+ result.warnings.push(`Could not verify tenant URL: ${error.message}`);
101
+ result.isValid = true; // Allow proceeding with warning
102
+ resolve(result);
103
+ });
104
+ });
105
+ }
106
+ /**
107
+ * Validates Benchling OAuth credentials
108
+ *
109
+ * @param tenant - Benchling tenant
110
+ * @param clientId - OAuth client ID
111
+ * @param clientSecret - OAuth client secret
112
+ * @returns Validation result
113
+ */
114
+ async function validateBenchlingCredentials(tenant, clientId, clientSecret) {
115
+ const result = {
116
+ isValid: false,
117
+ errors: [],
118
+ warnings: [],
119
+ };
120
+ if (!clientId || clientId.trim().length === 0) {
121
+ result.errors.push("Client ID cannot be empty");
122
+ }
123
+ if (!clientSecret || clientSecret.trim().length === 0) {
124
+ result.errors.push("Client secret cannot be empty");
125
+ }
126
+ if (result.errors.length > 0) {
127
+ return result;
128
+ }
129
+ // Test OAuth token endpoint
130
+ const tokenUrl = `https://${tenant}.benchling.com/api/v2/token`;
131
+ const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
132
+ return new Promise((resolve) => {
133
+ const postData = "grant_type=client_credentials";
134
+ const options = {
135
+ method: "POST",
136
+ headers: {
137
+ "Authorization": `Basic ${authString}`,
138
+ "Content-Type": "application/x-www-form-urlencoded",
139
+ "Content-Length": postData.length,
140
+ },
141
+ timeout: 10000,
142
+ };
143
+ const req = https.request(tokenUrl, options, (res) => {
144
+ let data = "";
145
+ res.on("data", (chunk) => {
146
+ data += chunk;
147
+ });
148
+ res.on("end", () => {
149
+ if (res.statusCode === 200) {
150
+ result.isValid = true;
151
+ console.log(" ✓ OAuth credentials validated successfully");
152
+ }
153
+ else {
154
+ result.errors.push(`OAuth validation failed with status ${res.statusCode}: ${data.substring(0, 100)}`);
155
+ }
156
+ resolve(result);
157
+ });
158
+ });
159
+ req.on("error", (error) => {
160
+ result.warnings.push(`Could not validate OAuth credentials: ${error.message}`);
161
+ result.isValid = true; // Allow proceeding with warning
162
+ resolve(result);
163
+ });
164
+ req.write(postData);
165
+ req.end();
166
+ });
167
+ }
168
+ /**
169
+ * Validates S3 bucket access
170
+ *
171
+ * @param bucketName - S3 bucket name
172
+ * @param region - AWS region
173
+ * @param awsProfile - AWS profile to use
174
+ * @returns Validation result
175
+ */
176
+ async function validateS3BucketAccess(bucketName, region, awsProfile) {
177
+ const result = {
178
+ isValid: false,
179
+ errors: [],
180
+ warnings: [],
181
+ };
182
+ if (!bucketName || bucketName.trim().length === 0) {
183
+ result.errors.push("Bucket name cannot be empty");
184
+ return result;
185
+ }
186
+ try {
187
+ const clientConfig = { region };
188
+ if (awsProfile) {
189
+ const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
190
+ clientConfig.credentials = fromIni({ profile: awsProfile });
191
+ }
192
+ const s3Client = new client_s3_1.S3Client(clientConfig);
193
+ // Test HeadBucket (verify bucket exists and we have access)
194
+ const headCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName });
195
+ await s3Client.send(headCommand);
196
+ console.log(` ✓ S3 bucket accessible: ${bucketName}`);
197
+ // Test ListObjects (verify we can list objects)
198
+ const listCommand = new client_s3_1.ListObjectsV2Command({
199
+ Bucket: bucketName,
200
+ MaxKeys: 1,
201
+ });
202
+ await s3Client.send(listCommand);
203
+ console.log(" ✓ S3 bucket list permission confirmed");
204
+ result.isValid = true;
205
+ }
206
+ catch (error) {
207
+ const err = error;
208
+ result.errors.push(`S3 bucket validation failed: ${err.message}`);
209
+ }
210
+ return result;
211
+ }
212
+ /**
213
+ * Verifies CDK deployment account using AWS STS
214
+ *
215
+ * @param region - AWS region for STS client
216
+ * @param awsProfile - AWS profile to use (optional)
217
+ * @returns AWS account ID
218
+ */
219
+ async function verifyCDKDeploymentAccount(region, awsProfile) {
220
+ try {
221
+ const clientConfig = { region };
222
+ if (awsProfile) {
223
+ const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
224
+ clientConfig.credentials = fromIni({ profile: awsProfile });
225
+ }
226
+ const stsClient = new client_sts_1.STSClient(clientConfig);
227
+ const response = await stsClient.send(new client_sts_1.GetCallerIdentityCommand({}));
228
+ const accountId = response.Account;
229
+ console.log(` ✓ CDK deployment account verified: ${accountId}`);
230
+ return accountId;
231
+ }
232
+ catch (error) {
233
+ throw new Error(`Failed to verify AWS account: ${error.message}`);
234
+ }
235
+ }
236
+ /**
237
+ * Finds catalog region by fetching config.json from QuiltWebHost
238
+ *
239
+ * @param catalogUrl - Quilt catalog URL (QuiltWebHost)
240
+ * @returns AWS region string or null if unable to determine
241
+ */
242
+ async function findCatalogRegion(catalogUrl) {
243
+ if (!catalogUrl || catalogUrl.trim().length === 0) {
244
+ return null;
245
+ }
246
+ // Validate URL format
247
+ try {
248
+ new URL(catalogUrl);
249
+ }
250
+ catch {
251
+ console.warn(` ⚠ Invalid catalog URL format: ${catalogUrl}`);
252
+ return null;
253
+ }
254
+ // Fetch config.json from QuiltWebHost
255
+ const configUrl = `${catalogUrl}/config.json`;
256
+ return new Promise((resolve) => {
257
+ https
258
+ .get(configUrl, { timeout: 10000 }, (res) => {
259
+ let data = "";
260
+ res.on("data", (chunk) => {
261
+ data += chunk;
262
+ });
263
+ res.on("end", () => {
264
+ if (res.statusCode === 200) {
265
+ try {
266
+ const config = JSON.parse(data);
267
+ // Quilt config.json has direct "region" field
268
+ const region = config.region;
269
+ if (region && typeof region === "string") {
270
+ console.log(` ✓ Found catalog region: ${region}`);
271
+ resolve(region);
272
+ }
273
+ else {
274
+ console.warn(" ⚠ No region field in catalog config.json");
275
+ resolve(null);
276
+ }
277
+ }
278
+ catch (error) {
279
+ console.warn(` ⚠ Failed to parse catalog config.json: ${error.message}`);
280
+ resolve(null);
281
+ }
282
+ }
283
+ else {
284
+ console.warn(` ⚠ Catalog config.json returned status ${res.statusCode}`);
285
+ resolve(null);
286
+ }
287
+ });
288
+ })
289
+ .on("error", (error) => {
290
+ console.warn(` ⚠ Could not fetch catalog config: ${error.message}`);
291
+ resolve(null);
292
+ });
293
+ });
294
+ }
295
+ /**
296
+ * Runs the interactive configuration wizard
297
+ *
298
+ * @param options - Wizard options
299
+ * @returns Completed user configuration
300
+ */
301
+ async function runInstallWizard(options = {}) {
302
+ const { profile = "default", nonInteractive = false, skipValidation = false, awsProfile, awsRegion = "us-east-1" } = options;
303
+ console.log("╔═══════════════════════════════════════════════════════════╗");
304
+ console.log("║ Benchling Webhook Configuration Wizard ║");
305
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
306
+ // Load existing configuration if available
307
+ const xdgConfig = new xdg_config_1.XDGConfig();
308
+ let existingConfig = {};
309
+ try {
310
+ existingConfig = xdgConfig.readProfileConfig("user", profile);
311
+ console.log("✓ Loaded existing configuration\n");
312
+ }
313
+ catch {
314
+ console.log("No existing configuration found, starting fresh\n");
315
+ }
316
+ const config = {
317
+ ...existingConfig, // Merge existing config as defaults
318
+ _metadata: {
319
+ source: "install-wizard",
320
+ savedAt: new Date().toISOString(),
321
+ version: "0.6.0",
322
+ },
323
+ };
324
+ // Step 1: Infer Quilt configuration
325
+ console.log("Step 1: Inferring Quilt configuration...\n");
326
+ // Step 1a: Try to get catalog URL from quilt3 CLI first
327
+ let catalogRegion = awsRegion;
328
+ try {
329
+ const { execSync } = await Promise.resolve().then(() => __importStar(require("child_process")));
330
+ const catalogUrl = execSync("quilt3 config", { encoding: "utf-8", stdio: ["pipe", "pipe", "ignore"] }).trim();
331
+ if (catalogUrl && catalogUrl.startsWith("http")) {
332
+ console.log(`Found quilt3 CLI catalog: ${catalogUrl}`);
333
+ // Fetch region from catalog config.json
334
+ const detectedRegion = await findCatalogRegion(catalogUrl);
335
+ if (detectedRegion) {
336
+ catalogRegion = detectedRegion;
337
+ console.log(`Using catalog region: ${catalogRegion}`);
338
+ }
339
+ }
340
+ }
341
+ catch {
342
+ // quilt3 not available or failed, continue with default region
343
+ console.log(`No quilt3 CLI found, using default region: ${catalogRegion}`);
344
+ }
345
+ const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
346
+ region: catalogRegion,
347
+ profile: awsProfile,
348
+ interactive: !nonInteractive,
349
+ });
350
+ const derivedConfig = (0, infer_quilt_config_1.inferenceResultToDerivedConfig)(inferenceResult);
351
+ // Merge inferred config
352
+ Object.assign(config, derivedConfig);
353
+ console.log("\n✓ Quilt configuration inferred");
354
+ // Step 2: Display and confirm Quilt stack configuration
355
+ if (!nonInteractive) {
356
+ console.log("\nStep 2: Verify Quilt Stack Configuration\n");
357
+ console.log("Detected Quilt stack:");
358
+ // Use parseStackArn and extractStackOutputs to get complete stack info
359
+ if (config.quiltStackArn) {
360
+ try {
361
+ const parsedArn = (0, config_resolver_1.parseStackArn)(config.quiltStackArn);
362
+ console.log(` Stack Name: ${parsedArn.stackName}`);
363
+ console.log(` Stack ARN: ${config.quiltStackArn}`);
364
+ console.log(` Region: ${parsedArn.region}`);
365
+ console.log(` Account: ${parsedArn.account}`);
366
+ // Fetch stack outputs using the config-resolver module
367
+ try {
368
+ const clientConfig = {
369
+ region: parsedArn.region,
370
+ };
371
+ if (config.awsProfile || awsProfile) {
372
+ const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
373
+ clientConfig.credentials = fromIni({ profile: config.awsProfile || awsProfile });
374
+ }
375
+ const cfClient = new client_cloudformation_1.CloudFormationClient(clientConfig);
376
+ const outputs = await (0, config_resolver_1.extractStackOutputs)(cfClient, parsedArn.stackName);
377
+ console.log(` Catalog URL: ${outputs.QuiltWebHost || "Not found"}`);
378
+ console.log(` User Database: ${outputs.UserAthenaDatabaseName || outputs.AthenaDatabase || "Not found"}`);
379
+ console.log(` Queue ARN: ${outputs.PackagerQueueArn || outputs.QueueArn || "Not found"}`);
380
+ }
381
+ catch (outputError) {
382
+ console.warn(` ⚠ Could not fetch stack outputs: ${outputError.message}`);
383
+ console.log(` Catalog URL: ${config.quiltCatalog || "Not found"}`);
384
+ console.log(` Queue ARN: ${config.queueArn || "Not found"}`);
385
+ }
386
+ }
387
+ catch {
388
+ // Fall back to simple display if parsing fails
389
+ console.log(` Stack ARN: ${config.quiltStackArn}`);
390
+ console.log(` Region: ${config.quiltRegion || awsRegion}`);
391
+ console.log(` Catalog URL: ${config.quiltCatalog || "Not found"}`);
392
+ console.log(` Queue ARN: ${config.queueArn || "Not found"}`);
393
+ }
394
+ }
395
+ else {
396
+ console.log(" Stack ARN: Not found");
397
+ console.log(` Region: ${config.quiltRegion || awsRegion}`);
398
+ }
399
+ const { confirmStack } = await inquirer_1.default.prompt([
400
+ {
401
+ type: "confirm",
402
+ name: "confirmStack",
403
+ message: "Is this the correct Quilt stack?",
404
+ default: true,
405
+ },
406
+ ]);
407
+ if (!confirmStack) {
408
+ console.log("\nPlease run the wizard again and select the correct stack.");
409
+ process.exit(0);
410
+ }
411
+ }
412
+ // Step 3: Benchling configuration
413
+ console.log("\nStep 3: Benchling configuration\n");
414
+ if (!nonInteractive) {
415
+ const benchlingAnswers = await inquirer_1.default.prompt([
416
+ {
417
+ type: "input",
418
+ name: "benchlingTenant",
419
+ message: "Benchling Tenant:",
420
+ default: config.benchlingTenant,
421
+ validate: (input) => input.trim().length > 0 || "Tenant is required",
422
+ },
423
+ {
424
+ type: "input",
425
+ name: "benchlingClientId",
426
+ message: "Benchling OAuth Client ID:",
427
+ default: config.benchlingClientId,
428
+ validate: (input) => input.trim().length > 0 || "Client ID is required",
429
+ },
430
+ {
431
+ type: "password",
432
+ name: "benchlingClientSecret",
433
+ message: config.benchlingClientSecret
434
+ ? "Benchling OAuth Client Secret (press Enter to keep existing):"
435
+ : "Benchling OAuth Client Secret:",
436
+ validate: (input) => {
437
+ // If there's an existing secret and input is empty, we'll keep the existing one
438
+ if (config.benchlingClientSecret && input.trim().length === 0) {
439
+ return true;
440
+ }
441
+ return input.trim().length > 0 || "Client secret is required";
442
+ },
443
+ },
444
+ {
445
+ type: "input",
446
+ name: "benchlingAppDefinitionId",
447
+ message: "Benchling App Definition ID:",
448
+ default: config.benchlingAppDefinitionId,
449
+ validate: (input) => input.trim().length > 0 || "App definition ID is required",
450
+ },
451
+ {
452
+ type: "input",
453
+ name: "benchlingPkgBucket",
454
+ message: "Benchling Package S3 Bucket:",
455
+ default: config.benchlingPkgBucket || config.quiltUserBucket,
456
+ validate: (input) => input.trim().length > 0 || "Bucket name is required",
457
+ },
458
+ ]);
459
+ // Handle empty password input - keep existing secret if user pressed Enter
460
+ if (benchlingAnswers.benchlingClientSecret.trim().length === 0 && config.benchlingClientSecret) {
461
+ benchlingAnswers.benchlingClientSecret = config.benchlingClientSecret;
462
+ }
463
+ Object.assign(config, benchlingAnswers);
464
+ // Validate Benchling configuration
465
+ if (!skipValidation && config.benchlingTenant) {
466
+ const tenantValidation = await validateBenchlingTenant(config.benchlingTenant);
467
+ if (!tenantValidation.isValid) {
468
+ console.error("\n❌ Benchling tenant validation failed:");
469
+ tenantValidation.errors.forEach((err) => console.error(` - ${err}`));
470
+ const { proceed } = await inquirer_1.default.prompt([
471
+ {
472
+ type: "confirm",
473
+ name: "proceed",
474
+ message: "Continue anyway?",
475
+ default: false,
476
+ },
477
+ ]);
478
+ if (!proceed) {
479
+ throw new Error("Configuration aborted by user");
480
+ }
481
+ }
482
+ if (tenantValidation.warnings.length > 0) {
483
+ console.warn("\n⚠ Warnings:");
484
+ tenantValidation.warnings.forEach((warn) => console.warn(` - ${warn}`));
485
+ }
486
+ // Validate OAuth credentials
487
+ if (config.benchlingClientId && config.benchlingClientSecret) {
488
+ const credValidation = await validateBenchlingCredentials(config.benchlingTenant, config.benchlingClientId, config.benchlingClientSecret);
489
+ if (!credValidation.isValid) {
490
+ console.error("\n❌ Benchling OAuth credential validation failed:");
491
+ credValidation.errors.forEach((err) => console.error(` - ${err}`));
492
+ const { proceed } = await inquirer_1.default.prompt([
493
+ {
494
+ type: "confirm",
495
+ name: "proceed",
496
+ message: "Continue anyway?",
497
+ default: false,
498
+ },
499
+ ]);
500
+ if (!proceed) {
501
+ throw new Error("Configuration aborted by user");
502
+ }
503
+ }
504
+ if (credValidation.warnings.length > 0) {
505
+ console.warn("\n⚠ Warnings:");
506
+ credValidation.warnings.forEach((warn) => console.warn(` - ${warn}`));
507
+ }
508
+ }
509
+ // Validate Benchling package bucket
510
+ if (config.benchlingPkgBucket) {
511
+ const bucketValidation = await validateS3BucketAccess(config.benchlingPkgBucket, config.quiltRegion || awsRegion, awsProfile);
512
+ if (!bucketValidation.isValid) {
513
+ console.error("\n❌ Benchling package bucket validation failed:");
514
+ bucketValidation.errors.forEach((err) => console.error(` - ${err}`));
515
+ const { proceed } = await inquirer_1.default.prompt([
516
+ {
517
+ type: "confirm",
518
+ name: "proceed",
519
+ message: "Continue anyway?",
520
+ default: false,
521
+ },
522
+ ]);
523
+ if (!proceed) {
524
+ throw new Error("Configuration aborted by user");
525
+ }
526
+ }
527
+ }
528
+ }
529
+ }
530
+ else {
531
+ // Non-interactive mode: use existing config values
532
+ // Required fields must already be set in XDG config
533
+ if (!config.benchlingTenant || !config.benchlingClientId || !config.benchlingClientSecret) {
534
+ throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured in XDG config. Run 'npm run setup' interactively first.");
535
+ }
536
+ // Set default bucket if not specified
537
+ if (!config.benchlingPkgBucket) {
538
+ config.benchlingPkgBucket = config.quiltUserBucket;
539
+ }
540
+ }
541
+ // Step 4: AWS configuration
542
+ console.log("\nStep 4: AWS configuration\n");
543
+ if (!nonInteractive) {
544
+ const awsAnswers = await inquirer_1.default.prompt([
545
+ {
546
+ type: "input",
547
+ name: "awsProfile",
548
+ message: "AWS Profile (optional, leave empty for default):",
549
+ default: awsProfile || "",
550
+ },
551
+ {
552
+ type: "input",
553
+ name: "cdkRegion",
554
+ message: "CDK Deployment Region:",
555
+ default: config.quiltRegion || awsRegion,
556
+ },
557
+ ]);
558
+ if (awsAnswers.awsProfile) {
559
+ config.awsProfile = awsAnswers.awsProfile;
560
+ }
561
+ config.cdkRegion = awsAnswers.cdkRegion;
562
+ // Verify CDK deployment account
563
+ console.log("\nVerifying CDK deployment account...");
564
+ try {
565
+ const accountId = await verifyCDKDeploymentAccount(awsAnswers.cdkRegion, config.awsProfile);
566
+ config.cdkAccount = accountId;
567
+ }
568
+ catch (error) {
569
+ console.error(`\n❌ Failed to verify AWS account: ${error.message}`);
570
+ const { proceed } = await inquirer_1.default.prompt([
571
+ {
572
+ type: "confirm",
573
+ name: "proceed",
574
+ message: "Continue anyway?",
575
+ default: false,
576
+ },
577
+ ]);
578
+ if (!proceed) {
579
+ throw new Error("Configuration aborted by user");
580
+ }
581
+ }
582
+ }
583
+ else {
584
+ // Non-interactive mode: use existing config or default region
585
+ if (!config.cdkRegion) {
586
+ config.cdkRegion = config.quiltRegion || awsRegion;
587
+ }
588
+ }
589
+ // Step 5: Optional configuration
590
+ console.log("\nStep 5: Optional configuration\n");
591
+ if (!nonInteractive) {
592
+ const optionalAnswers = await inquirer_1.default.prompt([
593
+ {
594
+ type: "input",
595
+ name: "pkgPrefix",
596
+ message: "Package S3 prefix (default: benchling):",
597
+ default: "benchling",
598
+ },
599
+ {
600
+ type: "input",
601
+ name: "pkgKey",
602
+ message: "Package metadata key (default: experiment_id):",
603
+ default: "experiment_id",
604
+ },
605
+ {
606
+ type: "list",
607
+ name: "logLevel",
608
+ message: "Log level:",
609
+ choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
610
+ default: "INFO",
611
+ },
612
+ {
613
+ type: "input",
614
+ name: "benchlingTestEntry",
615
+ message: "Benchling Test Entry ID (optional, for validation):",
616
+ default: config.benchlingTestEntry || "",
617
+ },
618
+ ]);
619
+ Object.assign(config, optionalAnswers);
620
+ // Remove empty benchlingTestEntry if not provided
621
+ if (!config.benchlingTestEntry || config.benchlingTestEntry.trim() === "") {
622
+ delete config.benchlingTestEntry;
623
+ }
624
+ }
625
+ else {
626
+ // Non-interactive mode: use existing config or defaults
627
+ if (!config.pkgPrefix) {
628
+ config.pkgPrefix = "benchling";
629
+ }
630
+ if (!config.pkgKey) {
631
+ config.pkgKey = "experiment_id";
632
+ }
633
+ if (!config.logLevel) {
634
+ config.logLevel = "INFO";
635
+ }
636
+ // benchlingTestEntry is optional, keep existing value if present
637
+ }
638
+ // Step 6: Save configuration
639
+ console.log("\nStep 6: Saving configuration...\n");
640
+ xdgConfig.ensureProfileDirectories(profile);
641
+ xdgConfig.writeProfileConfig("user", config, profile);
642
+ console.log(`✓ Configuration saved to profile: ${profile}`);
643
+ // Step 7: Sync secrets to AWS Secrets Manager
644
+ if (!nonInteractive) {
645
+ const { syncSecrets } = await inquirer_1.default.prompt([
646
+ {
647
+ type: "confirm",
648
+ name: "syncSecrets",
649
+ message: "Sync secrets to AWS Secrets Manager?",
650
+ default: true,
651
+ },
652
+ ]);
653
+ if (syncSecrets) {
654
+ console.log("\nSyncing secrets to AWS Secrets Manager...\n");
655
+ await (0, sync_secrets_1.syncSecretsToAWS)({
656
+ profile,
657
+ awsProfile: config.awsProfile,
658
+ region: config.cdkRegion || awsRegion,
659
+ });
660
+ console.log("\n✓ Secrets synced successfully");
661
+ }
662
+ }
663
+ console.log("\n╔═══════════════════════════════════════════════════════════╗");
664
+ console.log("║ Configuration Complete! ║");
665
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
666
+ return config;
667
+ }
668
+ /**
669
+ * Main execution for CLI usage
670
+ */
671
+ async function main() {
672
+ const args = process.argv.slice(2);
673
+ const options = {};
674
+ // Parse command line arguments
675
+ for (let i = 0; i < args.length; i++) {
676
+ if (args[i] === "--profile" && i + 1 < args.length) {
677
+ options.profile = args[i + 1];
678
+ i++;
679
+ }
680
+ else if (args[i] === "--non-interactive") {
681
+ options.nonInteractive = true;
682
+ }
683
+ else if (args[i] === "--skip-validation") {
684
+ options.skipValidation = true;
685
+ }
686
+ else if (args[i] === "--aws-profile" && i + 1 < args.length) {
687
+ options.awsProfile = args[i + 1];
688
+ i++;
689
+ }
690
+ else if (args[i] === "--aws-region" && i + 1 < args.length) {
691
+ options.awsRegion = args[i + 1];
692
+ i++;
693
+ }
694
+ else if (args[i] === "--help") {
695
+ console.log("Usage: install-wizard [options]");
696
+ console.log("\nOptions:");
697
+ console.log(" --profile <name> Configuration profile name (default: default)");
698
+ console.log(" --non-interactive Run in non-interactive mode (CI/CD)");
699
+ console.log(" --skip-validation Skip validation checks");
700
+ console.log(" --aws-profile <profile> AWS profile to use");
701
+ console.log(" --aws-region <region> AWS region (default: us-east-1)");
702
+ console.log(" --help Show this help message");
703
+ process.exit(0);
704
+ }
705
+ }
706
+ try {
707
+ await runInstallWizard(options);
708
+ process.exit(0);
709
+ }
710
+ catch (error) {
711
+ console.error("\n❌ Configuration failed:", error.message);
712
+ process.exit(1);
713
+ }
714
+ }
715
+ // Run main if executed directly
716
+ if (require.main === module) {
717
+ main();
718
+ }
719
+ //# sourceMappingURL=install-wizard.js.map