@quiltdata/benchling-webhook 0.6.3-20251104T170954Z → 0.7.1-20251106T100426Z

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 (159) hide show
  1. package/README.md +166 -5
  2. package/dist/bin/benchling-webhook.d.ts +2 -16
  3. package/dist/bin/benchling-webhook.d.ts.map +1 -1
  4. package/dist/bin/benchling-webhook.js +96 -158
  5. package/dist/bin/benchling-webhook.js.map +1 -1
  6. package/dist/bin/cli.js +96 -8
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/bin/{config-profiles.d.ts → commands/config-profiles.d.ts} +10 -9
  9. package/dist/bin/commands/config-profiles.d.ts.map +1 -0
  10. package/dist/bin/{config-profiles.js → commands/config-profiles.js} +109 -102
  11. package/dist/bin/commands/config-profiles.js.map +1 -0
  12. package/dist/bin/commands/create-secret.d.ts.map +1 -0
  13. package/dist/bin/commands/create-secret.js.map +1 -0
  14. package/dist/bin/commands/deploy.d.ts +12 -1
  15. package/dist/bin/commands/deploy.d.ts.map +1 -1
  16. package/dist/bin/commands/deploy.js +117 -107
  17. package/dist/bin/commands/deploy.js.map +1 -1
  18. package/dist/bin/{get-env.d.ts → commands/get-env.d.ts} +1 -1
  19. package/dist/bin/commands/get-env.d.ts.map +1 -0
  20. package/dist/bin/{get-env.js → commands/get-env.js} +2 -2
  21. package/dist/bin/commands/get-env.js.map +1 -0
  22. package/dist/bin/commands/health-check.d.ts +47 -0
  23. package/dist/bin/commands/health-check.d.ts.map +1 -0
  24. package/dist/bin/commands/health-check.js +357 -0
  25. package/dist/bin/commands/health-check.js.map +1 -0
  26. package/dist/{scripts → bin/commands}/infer-quilt-config.d.ts +6 -15
  27. package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -0
  28. package/dist/{scripts → bin/commands}/infer-quilt-config.js +37 -63
  29. package/dist/bin/commands/infer-quilt-config.js.map +1 -0
  30. package/dist/bin/commands/init.d.ts.map +1 -1
  31. package/dist/bin/commands/init.js +2 -32
  32. package/dist/bin/commands/init.js.map +1 -1
  33. package/dist/bin/commands/publish.d.ts.map +1 -0
  34. package/dist/bin/{publish.js → commands/publish.js} +2 -2
  35. package/dist/bin/commands/publish.js.map +1 -0
  36. package/dist/bin/commands/setup-profile.d.ts +29 -0
  37. package/dist/bin/commands/setup-profile.d.ts.map +1 -0
  38. package/dist/bin/commands/setup-profile.js +218 -0
  39. package/dist/bin/commands/setup-profile.js.map +1 -0
  40. package/dist/bin/commands/setup-wizard.d.ts +24 -11
  41. package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
  42. package/dist/bin/commands/setup-wizard.js +607 -51
  43. package/dist/bin/commands/setup-wizard.js.map +1 -1
  44. package/dist/{scripts → bin/commands}/sync-secrets.d.ts +6 -1
  45. package/dist/bin/commands/sync-secrets.d.ts.map +1 -0
  46. package/dist/{scripts → bin/commands}/sync-secrets.js +159 -55
  47. package/dist/bin/commands/sync-secrets.js.map +1 -0
  48. package/dist/bin/commands/validate.d.ts.map +1 -1
  49. package/dist/bin/commands/validate.js +2 -12
  50. package/dist/bin/commands/validate.js.map +1 -1
  51. package/dist/lib/alb-api-gateway.d.ts +7 -1
  52. package/dist/lib/alb-api-gateway.d.ts.map +1 -1
  53. package/dist/lib/alb-api-gateway.js +9 -6
  54. package/dist/lib/alb-api-gateway.js.map +1 -1
  55. package/dist/lib/benchling-webhook-stack.d.ts +13 -12
  56. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  57. package/dist/lib/benchling-webhook-stack.js +44 -31
  58. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  59. package/dist/lib/configuration-saver.d.ts +4 -24
  60. package/dist/lib/configuration-saver.d.ts.map +1 -1
  61. package/dist/lib/configuration-saver.js +14 -71
  62. package/dist/lib/configuration-saver.js.map +1 -1
  63. package/dist/lib/fargate-service.d.ts +11 -21
  64. package/dist/lib/fargate-service.d.ts.map +1 -1
  65. package/dist/lib/fargate-service.js +79 -176
  66. package/dist/lib/fargate-service.js.map +1 -1
  67. package/dist/lib/types/config.d.ts +538 -232
  68. package/dist/lib/types/config.d.ts.map +1 -1
  69. package/dist/lib/types/config.js +133 -3
  70. package/dist/lib/types/config.js.map +1 -1
  71. package/dist/lib/utils/config-loader.d.ts.map +1 -1
  72. package/dist/lib/utils/config-loader.js +3 -2
  73. package/dist/lib/utils/config-loader.js.map +1 -1
  74. package/dist/lib/utils/config-resolver.d.ts +2 -2
  75. package/dist/lib/utils/config-resolver.d.ts.map +1 -1
  76. package/dist/lib/utils/config-resolver.js +14 -7
  77. package/dist/lib/utils/config-resolver.js.map +1 -1
  78. package/dist/lib/utils/config.d.ts +1 -1
  79. package/dist/lib/utils/config.d.ts.map +1 -1
  80. package/dist/lib/utils/config.js +7 -3
  81. package/dist/lib/utils/config.js.map +1 -1
  82. package/dist/lib/utils/sqs.d.ts +13 -0
  83. package/dist/lib/utils/sqs.d.ts.map +1 -0
  84. package/dist/lib/utils/sqs.js +22 -0
  85. package/dist/lib/utils/sqs.js.map +1 -0
  86. package/dist/lib/utils/stack-inference.d.ts.map +1 -1
  87. package/dist/lib/utils/stack-inference.js +8 -6
  88. package/dist/lib/utils/stack-inference.js.map +1 -1
  89. package/dist/lib/xdg-config.d.ts +224 -106
  90. package/dist/lib/xdg-config.d.ts.map +1 -1
  91. package/dist/lib/xdg-config.js +454 -387
  92. package/dist/lib/xdg-config.js.map +1 -1
  93. package/dist/package.json +19 -14
  94. package/dist/scripts/check-logs.d.ts +12 -0
  95. package/dist/scripts/check-logs.d.ts.map +1 -0
  96. package/dist/{bin → scripts}/check-logs.js +65 -15
  97. package/dist/scripts/check-logs.js.map +1 -0
  98. package/dist/scripts/check-webhook-verification.d.ts +3 -0
  99. package/dist/scripts/check-webhook-verification.d.ts.map +1 -0
  100. package/dist/{bin/test-invalid-signature.js → scripts/check-webhook-verification.js} +1 -1
  101. package/dist/scripts/check-webhook-verification.js.map +1 -0
  102. package/dist/scripts/get-dev-version.d.ts +16 -0
  103. package/dist/scripts/get-dev-version.d.ts.map +1 -0
  104. package/dist/scripts/get-dev-version.js +57 -0
  105. package/dist/scripts/get-dev-version.js.map +1 -0
  106. package/dist/scripts/send-event.d.ts.map +1 -0
  107. package/dist/scripts/send-event.js.map +1 -0
  108. package/dist/{bin → scripts}/version.d.ts +3 -1
  109. package/dist/scripts/version.d.ts.map +1 -0
  110. package/dist/{bin → scripts}/version.js +95 -9
  111. package/dist/scripts/version.js.map +1 -0
  112. package/package.json +19 -14
  113. package/dist/bin/check-logs.d.ts +0 -7
  114. package/dist/bin/check-logs.d.ts.map +0 -1
  115. package/dist/bin/check-logs.js.map +0 -1
  116. package/dist/bin/config-profiles.d.ts.map +0 -1
  117. package/dist/bin/config-profiles.js.map +0 -1
  118. package/dist/bin/create-secret.d.ts.map +0 -1
  119. package/dist/bin/create-secret.js.map +0 -1
  120. package/dist/bin/dev-deploy.d.ts +0 -20
  121. package/dist/bin/dev-deploy.d.ts.map +0 -1
  122. package/dist/bin/dev-deploy.js +0 -289
  123. package/dist/bin/dev-deploy.js.map +0 -1
  124. package/dist/bin/get-env.d.ts.map +0 -1
  125. package/dist/bin/get-env.js.map +0 -1
  126. package/dist/bin/publish.d.ts.map +0 -1
  127. package/dist/bin/publish.js.map +0 -1
  128. package/dist/bin/release.d.ts +0 -11
  129. package/dist/bin/release.d.ts.map +0 -1
  130. package/dist/bin/release.js +0 -141
  131. package/dist/bin/release.js.map +0 -1
  132. package/dist/bin/send-event.d.ts.map +0 -1
  133. package/dist/bin/send-event.js.map +0 -1
  134. package/dist/bin/test-invalid-signature.d.ts +0 -3
  135. package/dist/bin/test-invalid-signature.d.ts.map +0 -1
  136. package/dist/bin/test-invalid-signature.js.map +0 -1
  137. package/dist/bin/version.d.ts.map +0 -1
  138. package/dist/bin/version.js.map +0 -1
  139. package/dist/lib/xdg-cli-wrapper.d.ts +0 -113
  140. package/dist/lib/xdg-cli-wrapper.d.ts.map +0 -1
  141. package/dist/lib/xdg-cli-wrapper.js +0 -289
  142. package/dist/lib/xdg-cli-wrapper.js.map +0 -1
  143. package/dist/scripts/config-health-check.d.ts +0 -84
  144. package/dist/scripts/config-health-check.d.ts.map +0 -1
  145. package/dist/scripts/config-health-check.js +0 -659
  146. package/dist/scripts/config-health-check.js.map +0 -1
  147. package/dist/scripts/infer-quilt-config.d.ts.map +0 -1
  148. package/dist/scripts/infer-quilt-config.js.map +0 -1
  149. package/dist/scripts/install-wizard.d.ts +0 -34
  150. package/dist/scripts/install-wizard.d.ts.map +0 -1
  151. package/dist/scripts/install-wizard.js +0 -719
  152. package/dist/scripts/install-wizard.js.map +0 -1
  153. package/dist/scripts/sync-secrets.d.ts.map +0 -1
  154. package/dist/scripts/sync-secrets.js.map +0 -1
  155. /package/dist/bin/{create-secret.d.ts → commands/create-secret.d.ts} +0 -0
  156. /package/dist/bin/{create-secret.js → commands/create-secret.js} +0 -0
  157. /package/dist/bin/{publish.d.ts → commands/publish.d.ts} +0 -0
  158. /package/dist/{bin → scripts}/send-event.d.ts +0 -0
  159. /package/dist/{bin → scripts}/send-event.js +0 -0
@@ -1,10 +1,16 @@
1
+ #!/usr/bin/env node
1
2
  "use strict";
2
3
  /**
3
- * Setup Wizard Command
4
+ * Interactive Configuration Wizard (v0.7.0)
4
5
  *
5
- * Launches the interactive setup wizard when npx is run without arguments.
6
- * This provides a guided experience for first-time users to configure and
7
- * deploy the Benchling webhook integration.
6
+ * Complete setup wizard that orchestrates:
7
+ * 1. Quilt configuration inference
8
+ * 2. Interactive configuration prompts
9
+ * 3. Configuration validation
10
+ * 4. Profile persistence via XDGConfig
11
+ *
12
+ * Consolidated from scripts/install-wizard.ts, scripts/config/wizard.ts,
13
+ * and scripts/config/validator.ts.
8
14
  *
9
15
  * @module commands/setup-wizard
10
16
  */
@@ -46,58 +52,608 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
46
52
  };
47
53
  Object.defineProperty(exports, "__esModule", { value: true });
48
54
  exports.setupWizardCommand = setupWizardCommand;
49
- const chalk_1 = __importDefault(require("chalk"));
50
- const install_wizard_1 = require("../../scripts/install-wizard");
55
+ const https = __importStar(require("https"));
56
+ const inquirer_1 = __importDefault(require("inquirer"));
57
+ const client_s3_1 = require("@aws-sdk/client-s3");
58
+ const xdg_config_1 = require("../../lib/xdg-config");
59
+ const infer_quilt_config_1 = require("../commands/infer-quilt-config");
60
+ const sqs_1 = require("../../lib/utils/sqs");
61
+ // =============================================================================
62
+ // VALIDATION FUNCTIONS (from scripts/config/validator.ts)
63
+ // =============================================================================
51
64
  /**
52
- * Setup wizard command handler
53
- *
54
- * Provides guided setup experience:
55
- * 1. Welcome message and prerequisites check
56
- * 2. Configuration collection via install-wizard
57
- * 3. Automatic deployment (optional)
58
- * 4. Integration testing (optional)
59
- *
60
- * @returns Promise that resolves when wizard completes
65
+ * Validates Benchling tenant accessibility
66
+ */
67
+ async function validateBenchlingTenant(tenant) {
68
+ const result = {
69
+ isValid: false,
70
+ errors: [],
71
+ warnings: [],
72
+ };
73
+ if (!tenant || tenant.trim().length === 0) {
74
+ result.errors.push("Tenant name cannot be empty");
75
+ return result;
76
+ }
77
+ // Basic format validation
78
+ if (!/^[a-zA-Z0-9-_]+$/.test(tenant)) {
79
+ result.errors.push("Tenant name contains invalid characters (only alphanumeric, dash, underscore allowed)");
80
+ return result;
81
+ }
82
+ // Test tenant URL accessibility
83
+ const tenantUrl = `https://${tenant}.benchling.com`;
84
+ return new Promise((resolve) => {
85
+ https
86
+ .get(tenantUrl, { timeout: 5000 }, (res) => {
87
+ if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
88
+ result.isValid = true;
89
+ console.log(` ✓ Tenant URL accessible: ${tenantUrl}`);
90
+ }
91
+ else {
92
+ if (!result.warnings)
93
+ result.warnings = [];
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
+ if (!result.warnings)
101
+ result.warnings = [];
102
+ result.warnings.push(`Could not verify tenant URL: ${error.message}`);
103
+ result.isValid = true; // Allow proceeding with warning
104
+ resolve(result);
105
+ });
106
+ });
107
+ }
108
+ /**
109
+ * Validates Benchling OAuth credentials
61
110
  */
62
- async function setupWizardCommand() {
63
- console.log(chalk_1.default.bold.cyan("\n🚀 Benchling Webhook Setup Wizard\n"));
64
- console.log("This wizard will guide you through:");
65
- console.log(" 1. Collecting configuration (Benchling credentials, AWS settings)");
66
- console.log(" 2. Validating credentials and access");
67
- console.log(" 3. Saving configuration for deployment");
68
- console.log(" 4. Deploying to AWS (optional)\n");
69
- console.log(chalk_1.default.dim("Press Ctrl+C at any time to exit\n"));
70
- // Run the existing install wizard
71
- await (0, install_wizard_1.runInstallWizard)({
72
- nonInteractive: false,
73
- skipValidation: false,
111
+ async function validateBenchlingCredentials(tenant, clientId, clientSecret) {
112
+ const result = {
113
+ isValid: false,
114
+ errors: [],
115
+ warnings: [],
116
+ };
117
+ if (!clientId || clientId.trim().length === 0) {
118
+ result.errors.push("Client ID cannot be empty");
119
+ }
120
+ if (!clientSecret || clientSecret.trim().length === 0) {
121
+ result.errors.push("Client secret cannot be empty");
122
+ }
123
+ if (result.errors.length > 0) {
124
+ return result;
125
+ }
126
+ // Test OAuth token endpoint
127
+ const tokenUrl = `https://${tenant}.benchling.com/api/v2/token`;
128
+ const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
129
+ return new Promise((resolve) => {
130
+ const postData = "grant_type=client_credentials";
131
+ const options = {
132
+ method: "POST",
133
+ headers: {
134
+ "Authorization": `Basic ${authString}`,
135
+ "Content-Type": "application/x-www-form-urlencoded",
136
+ "Content-Length": postData.length,
137
+ },
138
+ timeout: 10000,
139
+ };
140
+ const req = https.request(tokenUrl, options, (res) => {
141
+ let data = "";
142
+ res.on("data", (chunk) => {
143
+ data += chunk;
144
+ });
145
+ res.on("end", () => {
146
+ if (res.statusCode === 200) {
147
+ result.isValid = true;
148
+ console.log(" ✓ OAuth credentials validated successfully");
149
+ }
150
+ else {
151
+ result.errors.push(`OAuth validation failed with status ${res.statusCode}: ${data.substring(0, 100)}`);
152
+ }
153
+ resolve(result);
154
+ });
155
+ });
156
+ req.on("error", (error) => {
157
+ if (!result.warnings)
158
+ result.warnings = [];
159
+ result.warnings.push(`Could not validate OAuth credentials: ${error.message}`);
160
+ result.isValid = true; // Allow proceeding with warning
161
+ resolve(result);
162
+ });
163
+ req.write(postData);
164
+ req.end();
74
165
  });
75
- console.log(chalk_1.default.green.bold("\n✓ Setup complete!\n"));
76
- // Ask if user wants to deploy now
77
- const inquirer = (await Promise.resolve().then(() => __importStar(require("inquirer")))).default;
78
- const { shouldDeploy } = await inquirer.prompt([
166
+ }
167
+ /**
168
+ * Validates S3 bucket access
169
+ */
170
+ async function validateS3BucketAccess(bucketName, region, awsProfile) {
171
+ const result = {
172
+ isValid: false,
173
+ errors: [],
174
+ warnings: [],
175
+ };
176
+ if (!bucketName || bucketName.trim().length === 0) {
177
+ result.errors.push("Bucket name cannot be empty");
178
+ return result;
179
+ }
180
+ try {
181
+ const clientConfig = { region };
182
+ if (awsProfile) {
183
+ const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
184
+ clientConfig.credentials = fromIni({ profile: awsProfile });
185
+ }
186
+ const s3Client = new client_s3_1.S3Client(clientConfig);
187
+ // Test HeadBucket (verify bucket exists and we have access)
188
+ const headCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName });
189
+ await s3Client.send(headCommand);
190
+ console.log(` ✓ S3 bucket accessible: ${bucketName}`);
191
+ // Test ListObjects (verify we can list objects)
192
+ const listCommand = new client_s3_1.ListObjectsV2Command({
193
+ Bucket: bucketName,
194
+ MaxKeys: 1,
195
+ });
196
+ await s3Client.send(listCommand);
197
+ console.log(" ✓ S3 bucket list permission confirmed");
198
+ result.isValid = true;
199
+ }
200
+ catch (error) {
201
+ const err = error;
202
+ result.errors.push(`S3 bucket validation failed: ${err.message}`);
203
+ }
204
+ return result;
205
+ }
206
+ /**
207
+ * Validates complete ProfileConfig
208
+ */
209
+ async function validateConfig(config, options = {}) {
210
+ const result = {
211
+ isValid: true,
212
+ errors: [],
213
+ warnings: [],
214
+ };
215
+ if (options.skipValidation) {
216
+ return result;
217
+ }
218
+ // Validate Benchling tenant
219
+ const tenantValidation = await validateBenchlingTenant(config.benchling.tenant);
220
+ if (!tenantValidation.isValid) {
221
+ result.isValid = false;
222
+ result.errors.push(...tenantValidation.errors);
223
+ }
224
+ if (tenantValidation.warnings && tenantValidation.warnings.length > 0) {
225
+ if (!result.warnings)
226
+ result.warnings = [];
227
+ result.warnings.push(...tenantValidation.warnings);
228
+ }
229
+ // Validate OAuth credentials (if secret is provided)
230
+ if (config.benchling.clientSecret) {
231
+ const credValidation = await validateBenchlingCredentials(config.benchling.tenant, config.benchling.clientId, config.benchling.clientSecret);
232
+ if (!credValidation.isValid) {
233
+ result.isValid = false;
234
+ result.errors.push(...credValidation.errors);
235
+ }
236
+ if (credValidation.warnings && credValidation.warnings.length > 0) {
237
+ if (!result.warnings)
238
+ result.warnings = [];
239
+ result.warnings.push(...credValidation.warnings);
240
+ }
241
+ }
242
+ // Validate S3 bucket access
243
+ const bucketValidation = await validateS3BucketAccess(config.packages.bucket, config.deployment.region, options.awsProfile);
244
+ if (!bucketValidation.isValid) {
245
+ result.isValid = false;
246
+ result.errors.push(...bucketValidation.errors);
247
+ }
248
+ if (bucketValidation.warnings && bucketValidation.warnings.length > 0) {
249
+ if (!result.warnings)
250
+ result.warnings = [];
251
+ result.warnings.push(...bucketValidation.warnings);
252
+ }
253
+ return result;
254
+ }
255
+ /**
256
+ * Runs interactive configuration wizard
257
+ */
258
+ async function runConfigWizard(options = {}) {
259
+ const { existingConfig = {}, nonInteractive = false, inheritFrom } = options;
260
+ console.log("╔═══════════════════════════════════════════════════════════╗");
261
+ console.log("║ Benchling Webhook Configuration Wizard ║");
262
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
263
+ if (inheritFrom) {
264
+ console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
265
+ }
266
+ const config = { ...existingConfig };
267
+ let awsAccountId;
268
+ // If non-interactive, validate that all required fields are present
269
+ if (nonInteractive) {
270
+ if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
271
+ throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
272
+ }
273
+ // Add metadata and inheritance marker before returning
274
+ const now = new Date().toISOString();
275
+ const finalConfig = config;
276
+ finalConfig._metadata = {
277
+ version: "0.7.0",
278
+ createdAt: config._metadata?.createdAt || now,
279
+ updatedAt: now,
280
+ source: "wizard",
281
+ };
282
+ if (inheritFrom) {
283
+ finalConfig._inherits = inheritFrom;
284
+ }
285
+ return finalConfig;
286
+ }
287
+ // Prompt for Quilt configuration (if not inherited)
288
+ if (!inheritFrom) {
289
+ console.log("Step 1: Quilt Configuration\n");
290
+ const quiltAnswers = await inquirer_1.default.prompt([
291
+ {
292
+ type: "input",
293
+ name: "stackArn",
294
+ message: "Quilt Stack ARN:",
295
+ default: config.quilt?.stackArn,
296
+ validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:cloudformation:") ||
297
+ "Stack ARN is required and must start with arn:aws:cloudformation:",
298
+ },
299
+ {
300
+ type: "input",
301
+ name: "catalog",
302
+ message: "Quilt Catalog URL (domain or full URL):",
303
+ default: config.quilt?.catalog,
304
+ validate: (input) => {
305
+ const trimmed = input.trim();
306
+ if (trimmed.length === 0) {
307
+ return "Catalog URL is required";
308
+ }
309
+ return true;
310
+ },
311
+ filter: (input) => {
312
+ // Strip protocol if present, store only domain
313
+ return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
314
+ },
315
+ },
316
+ {
317
+ type: "input",
318
+ name: "database",
319
+ message: "Quilt Athena Database:",
320
+ default: config.quilt?.database || "quilt_catalog",
321
+ validate: (input) => input.trim().length > 0 || "Database name is required",
322
+ },
323
+ {
324
+ type: "input",
325
+ name: "queueUrl",
326
+ message: "SQS Queue URL:",
327
+ default: config.quilt?.queueUrl,
328
+ validate: (input) => {
329
+ return (0, sqs_1.isQueueUrl)(input) ||
330
+ "Queue URL is required and must look like https://sqs.<region>.amazonaws.com/<account>/<queue>";
331
+ },
332
+ },
333
+ ]);
334
+ // Extract region and account ID from stack ARN
335
+ // ARN format: arn:aws:cloudformation:REGION:ACCOUNT_ID:stack/STACK_NAME/STACK_ID
336
+ const arnMatch = quiltAnswers.stackArn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):/);
337
+ const quiltRegion = arnMatch ? arnMatch[1] : "us-east-1";
338
+ awsAccountId = arnMatch ? arnMatch[2] : undefined;
339
+ config.quilt = {
340
+ stackArn: quiltAnswers.stackArn,
341
+ catalog: quiltAnswers.catalog,
342
+ database: quiltAnswers.database,
343
+ queueUrl: quiltAnswers.queueUrl,
344
+ region: quiltRegion,
345
+ };
346
+ }
347
+ // Prompt for Benchling configuration
348
+ console.log("\nStep 2: Benchling Configuration\n");
349
+ const benchlingAnswers = await inquirer_1.default.prompt([
350
+ {
351
+ type: "input",
352
+ name: "tenant",
353
+ message: "Benchling Tenant:",
354
+ default: config.benchling?.tenant,
355
+ validate: (input) => input.trim().length > 0 || "Tenant is required",
356
+ },
357
+ {
358
+ type: "input",
359
+ name: "clientId",
360
+ message: "Benchling OAuth Client ID:",
361
+ default: config.benchling?.clientId,
362
+ validate: (input) => input.trim().length > 0 || "Client ID is required",
363
+ },
79
364
  {
80
- type: "confirm",
81
- name: "shouldDeploy",
82
- message: "Would you like to deploy to AWS now?",
83
- default: true,
365
+ type: "password",
366
+ name: "clientSecret",
367
+ message: config.benchling?.clientSecret
368
+ ? "Benchling OAuth Client Secret (press Enter to keep existing):"
369
+ : "Benchling OAuth Client Secret:",
370
+ validate: (input) => {
371
+ // If there's an existing secret and input is empty, we'll keep the existing one
372
+ if (config.benchling?.clientSecret && input.trim().length === 0) {
373
+ return true;
374
+ }
375
+ return input.trim().length > 0 || "Client secret is required";
376
+ },
377
+ },
378
+ {
379
+ type: "input",
380
+ name: "appDefinitionId",
381
+ message: "Benchling App Definition ID:",
382
+ default: config.benchling?.appDefinitionId,
383
+ validate: (input) => input.trim().length > 0 || "App definition ID is required",
384
+ },
385
+ {
386
+ type: "input",
387
+ name: "testEntryId",
388
+ message: "Benchling Test Entry ID (optional):",
389
+ default: config.benchling?.testEntryId || "",
390
+ },
391
+ ]);
392
+ // Handle empty password input - keep existing secret if user pressed Enter
393
+ if (benchlingAnswers.clientSecret.trim().length === 0 && config.benchling?.clientSecret) {
394
+ benchlingAnswers.clientSecret = config.benchling.clientSecret;
395
+ }
396
+ config.benchling = {
397
+ tenant: benchlingAnswers.tenant,
398
+ clientId: benchlingAnswers.clientId,
399
+ clientSecret: benchlingAnswers.clientSecret,
400
+ appDefinitionId: benchlingAnswers.appDefinitionId,
401
+ };
402
+ if (benchlingAnswers.testEntryId && benchlingAnswers.testEntryId.trim() !== "") {
403
+ config.benchling.testEntryId = benchlingAnswers.testEntryId;
404
+ }
405
+ // Prompt for package configuration
406
+ console.log("\nStep 3: Package Configuration\n");
407
+ const packageAnswers = await inquirer_1.default.prompt([
408
+ {
409
+ type: "input",
410
+ name: "bucket",
411
+ message: "Package S3 Bucket:",
412
+ default: config.packages?.bucket,
413
+ validate: (input) => input.trim().length > 0 || "Bucket name is required",
414
+ },
415
+ {
416
+ type: "input",
417
+ name: "prefix",
418
+ message: "Package S3 prefix:",
419
+ default: config.packages?.prefix || "benchling",
420
+ },
421
+ {
422
+ type: "input",
423
+ name: "metadataKey",
424
+ message: "Package metadata key:",
425
+ default: config.packages?.metadataKey || "experiment_id",
426
+ },
427
+ ]);
428
+ config.packages = {
429
+ bucket: packageAnswers.bucket,
430
+ prefix: packageAnswers.prefix,
431
+ metadataKey: packageAnswers.metadataKey,
432
+ };
433
+ // Prompt for deployment configuration
434
+ console.log("\nStep 4: Deployment Configuration\n");
435
+ const deploymentAnswers = await inquirer_1.default.prompt([
436
+ {
437
+ type: "input",
438
+ name: "region",
439
+ message: "AWS Deployment Region:",
440
+ default: config.deployment?.region || config.quilt?.region || "us-east-1",
441
+ },
442
+ {
443
+ type: "input",
444
+ name: "account",
445
+ message: "AWS Account ID:",
446
+ default: config.deployment?.account || awsAccountId || config.quilt?.stackArn?.match(/:(\d{12}):/)?.[1],
447
+ validate: (input) => {
448
+ if (!input || input.trim().length === 0) {
449
+ return "AWS Account ID is required";
450
+ }
451
+ if (!/^\d{12}$/.test(input.trim())) {
452
+ return "AWS Account ID must be a 12-digit number";
453
+ }
454
+ return true;
455
+ },
456
+ },
457
+ ]);
458
+ config.deployment = {
459
+ region: deploymentAnswers.region,
460
+ account: deploymentAnswers.account,
461
+ };
462
+ // Optional: Logging configuration
463
+ console.log("\nStep 5: Optional Configuration\n");
464
+ const optionalAnswers = await inquirer_1.default.prompt([
465
+ {
466
+ type: "list",
467
+ name: "logLevel",
468
+ message: "Log level:",
469
+ choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
470
+ default: config.logging?.level || "INFO",
471
+ },
472
+ {
473
+ type: "input",
474
+ name: "webhookAllowList",
475
+ message: "Webhook IP allowlist (comma-separated, empty for none):",
476
+ default: config.security?.webhookAllowList || "",
84
477
  },
85
478
  ]);
86
- if (shouldDeploy) {
87
- console.log(chalk_1.default.cyan("\n📦 Starting deployment...\n"));
88
- const { deployCommand } = await Promise.resolve().then(() => __importStar(require("./deploy")));
89
- await deployCommand({});
90
- console.log(chalk_1.default.green.bold("\n✓ Deployment complete!\n"));
91
- console.log("Next steps:");
92
- console.log(chalk_1.default.cyan(" • Set webhook URL in Benchling app settings"));
93
- console.log(chalk_1.default.cyan(" • Install the app in your Benchling tenant"));
94
- console.log(chalk_1.default.cyan(" • Test integration: npx @quiltdata/benchling-webhook test"));
95
- }
96
- else {
97
- console.log("\nNext steps:");
98
- console.log(chalk_1.default.cyan(" • Run deployment: npx @quiltdata/benchling-webhook deploy"));
99
- console.log(chalk_1.default.cyan(" • Test integration: npx @quiltdata/benchling-webhook test"));
100
- }
101
- console.log(chalk_1.default.dim("\nFor more information: https://github.com/quiltdata/benchling-webhook\n"));
479
+ config.logging = {
480
+ level: optionalAnswers.logLevel,
481
+ };
482
+ config.security = {
483
+ enableVerification: true,
484
+ webhookAllowList: optionalAnswers.webhookAllowList,
485
+ };
486
+ // Add metadata
487
+ const now = new Date().toISOString();
488
+ config._metadata = {
489
+ version: "0.7.0",
490
+ createdAt: config._metadata?.createdAt || now,
491
+ updatedAt: now,
492
+ source: "wizard",
493
+ };
494
+ // Add inheritance marker if specified
495
+ if (inheritFrom) {
496
+ config._inherits = inheritFrom;
497
+ }
498
+ return config;
499
+ }
500
+ /**
501
+ * Main install wizard function
502
+ *
503
+ * Orchestrates the complete configuration workflow:
504
+ * 1. Load existing configuration (if any)
505
+ * 2. Infer Quilt configuration from AWS
506
+ * 3. Run interactive prompts for missing fields
507
+ * 4. Validate configuration
508
+ * 5. Save to XDG config directory
509
+ */
510
+ async function runInstallWizard(options = {}) {
511
+ const { profile = "default", inheritFrom, nonInteractive = false, skipValidation = false, awsProfile, awsRegion = "us-east-1", } = options;
512
+ const xdg = new xdg_config_1.XDGConfig();
513
+ console.log("\n╔═══════════════════════════════════════════════════════════╗");
514
+ console.log("║ Benchling Webhook Setup (v0.7.0) ║");
515
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
516
+ // Step 1: Load existing configuration (if profile exists)
517
+ let existingConfig;
518
+ // Determine if we should inherit from 'default' when profile is not 'default'
519
+ const shouldInheritFromDefault = profile !== "default" && !inheritFrom;
520
+ const effectiveInheritFrom = inheritFrom || (shouldInheritFromDefault ? "default" : undefined);
521
+ if (xdg.profileExists(profile)) {
522
+ console.log(`Loading existing configuration for profile: ${profile}\n`);
523
+ try {
524
+ existingConfig = effectiveInheritFrom
525
+ ? xdg.readProfileWithInheritance(profile, effectiveInheritFrom)
526
+ : xdg.readProfile(profile);
527
+ }
528
+ catch (error) {
529
+ console.warn(`Warning: Could not load existing config: ${error.message}`);
530
+ }
531
+ }
532
+ else if (effectiveInheritFrom) {
533
+ // If profile doesn't exist but we should inherit, load base profile
534
+ console.log(`Creating new profile '${profile}' inheriting from '${effectiveInheritFrom}'\n`);
535
+ try {
536
+ existingConfig = xdg.readProfile(effectiveInheritFrom);
537
+ }
538
+ catch (error) {
539
+ throw new Error(`Base profile '${effectiveInheritFrom}' not found: ${error.message}`);
540
+ }
541
+ }
542
+ // Step 2: Infer Quilt configuration (unless inheriting from another profile)
543
+ let quiltConfig = existingConfig?.quilt || {};
544
+ let inferredAccountId;
545
+ if (!effectiveInheritFrom || !existingConfig?.quilt) {
546
+ console.log("Step 1: Inferring Quilt configuration from AWS...\n");
547
+ try {
548
+ const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
549
+ region: awsRegion,
550
+ profile: awsProfile,
551
+ interactive: !nonInteractive,
552
+ });
553
+ quiltConfig = inferenceResult;
554
+ inferredAccountId = inferenceResult.account;
555
+ console.log("✓ Quilt configuration inferred\n");
556
+ }
557
+ catch (error) {
558
+ console.error(`Failed to infer Quilt configuration: ${error.message}`);
559
+ if (nonInteractive) {
560
+ throw error;
561
+ }
562
+ const { continueManually } = await inquirer_1.default.prompt([
563
+ {
564
+ type: "confirm",
565
+ name: "continueManually",
566
+ message: "Continue and enter Quilt configuration manually?",
567
+ default: true,
568
+ },
569
+ ]);
570
+ if (!continueManually) {
571
+ throw new Error("Setup aborted by user");
572
+ }
573
+ }
574
+ }
575
+ // Merge inferred Quilt config with existing config
576
+ const partialConfig = {
577
+ ...existingConfig,
578
+ quilt: {
579
+ ...existingConfig?.quilt,
580
+ ...quiltConfig,
581
+ },
582
+ // Pass through inferred account ID for deployment config
583
+ deployment: {
584
+ ...existingConfig?.deployment,
585
+ account: existingConfig?.deployment?.account || inferredAccountId,
586
+ },
587
+ };
588
+ // Step 3: Run interactive wizard for remaining configuration
589
+ const config = await runConfigWizard({
590
+ existingConfig: partialConfig,
591
+ nonInteractive,
592
+ inheritFrom: effectiveInheritFrom,
593
+ });
594
+ // Step 4: Validate configuration
595
+ if (!skipValidation) {
596
+ console.log("\nValidating configuration...\n");
597
+ const validation = await validateConfig(config, {
598
+ skipValidation,
599
+ awsProfile,
600
+ });
601
+ if (!validation.isValid) {
602
+ console.error("\n❌ Configuration validation failed:");
603
+ validation.errors.forEach((err) => console.error(` - ${err}`));
604
+ if (nonInteractive) {
605
+ throw new Error("Configuration validation failed");
606
+ }
607
+ const { proceed } = await inquirer_1.default.prompt([
608
+ {
609
+ type: "confirm",
610
+ name: "proceed",
611
+ message: "Save configuration anyway?",
612
+ default: false,
613
+ },
614
+ ]);
615
+ if (!proceed) {
616
+ throw new Error("Setup aborted by user");
617
+ }
618
+ }
619
+ else {
620
+ console.log("✓ Configuration validated successfully\n");
621
+ }
622
+ if (validation.warnings && validation.warnings.length > 0) {
623
+ console.warn("\n⚠ Warnings:");
624
+ validation.warnings.forEach((warn) => console.warn(` - ${warn}`));
625
+ console.log("");
626
+ }
627
+ }
628
+ // Step 5: Save configuration
629
+ console.log(`Saving configuration to profile: ${profile}...\n`);
630
+ try {
631
+ xdg.writeProfile(profile, config);
632
+ console.log(`✓ Configuration saved to: ~/.config/benchling-webhook/${profile}/config.json\n`);
633
+ }
634
+ catch (error) {
635
+ throw new Error(`Failed to save configuration: ${error.message}`);
636
+ }
637
+ // Step 6: Display next steps
638
+ console.log("╔═══════════════════════════════════════════════════════════╗");
639
+ console.log("║ Setup Complete! ║");
640
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
641
+ console.log("Next steps:");
642
+ console.log(" 1. Sync secrets to AWS: npm run setup:sync-secrets");
643
+ console.log(" 2. Deploy to AWS: npm run deploy:dev");
644
+ console.log(" 3. Test integration: npm run test:dev\n");
645
+ return config;
646
+ }
647
+ // =============================================================================
648
+ // CLI COMMAND EXPORT
649
+ // =============================================================================
650
+ /**
651
+ * Setup wizard command handler
652
+ *
653
+ * @param options - Wizard options
654
+ * @returns Promise that resolves when wizard completes
655
+ */
656
+ async function setupWizardCommand(options = {}) {
657
+ await runInstallWizard(options);
102
658
  }
103
659
  //# sourceMappingURL=setup-wizard.js.map