@quiltdata/benchling-webhook 0.7.2-20251106T003353Z → 0.7.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/README.md +33 -159
  2. package/dist/bin/benchling-webhook.d.ts.map +1 -1
  3. package/dist/bin/benchling-webhook.js +2 -4
  4. package/dist/bin/benchling-webhook.js.map +1 -1
  5. package/dist/bin/commands/config-profiles.d.ts.map +1 -1
  6. package/dist/bin/commands/config-profiles.js +1 -2
  7. package/dist/bin/commands/config-profiles.js.map +1 -1
  8. package/dist/bin/commands/deploy.d.ts +1 -1
  9. package/dist/bin/commands/deploy.d.ts.map +1 -1
  10. package/dist/bin/commands/deploy.js +67 -9
  11. package/dist/bin/commands/deploy.js.map +1 -1
  12. package/dist/bin/commands/infer-quilt-config.d.ts +6 -15
  13. package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -1
  14. package/dist/bin/commands/infer-quilt-config.js +35 -64
  15. package/dist/bin/commands/infer-quilt-config.js.map +1 -1
  16. package/dist/bin/commands/manifest.d.ts +0 -11
  17. package/dist/bin/commands/manifest.d.ts.map +1 -1
  18. package/dist/bin/commands/manifest.js +8 -22
  19. package/dist/bin/commands/manifest.js.map +1 -1
  20. package/dist/bin/commands/setup-profile.d.ts.map +1 -1
  21. package/dist/bin/commands/setup-profile.js +15 -17
  22. package/dist/bin/commands/setup-profile.js.map +1 -1
  23. package/dist/bin/commands/setup-wizard.d.ts +0 -2
  24. package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
  25. package/dist/bin/commands/setup-wizard.js +172 -414
  26. package/dist/bin/commands/setup-wizard.js.map +1 -1
  27. package/dist/bin/commands/sync-secrets.js +2 -2
  28. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  29. package/dist/lib/benchling-webhook-stack.js +15 -1
  30. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  31. package/dist/lib/configuration-saver.d.ts +0 -8
  32. package/dist/lib/configuration-saver.d.ts.map +1 -1
  33. package/dist/lib/configuration-saver.js +1 -18
  34. package/dist/lib/configuration-saver.js.map +1 -1
  35. package/dist/lib/fargate-service.d.ts +4 -2
  36. package/dist/lib/fargate-service.d.ts.map +1 -1
  37. package/dist/lib/fargate-service.js +25 -12
  38. package/dist/lib/fargate-service.js.map +1 -1
  39. package/dist/lib/types/config.d.ts +9 -70
  40. package/dist/lib/types/config.d.ts.map +1 -1
  41. package/dist/lib/types/config.js +2 -3
  42. package/dist/lib/types/config.js.map +1 -1
  43. package/dist/lib/utils/config-loader.d.ts.map +1 -1
  44. package/dist/lib/utils/config-loader.js +3 -2
  45. package/dist/lib/utils/config-loader.js.map +1 -1
  46. package/dist/lib/utils/config-resolver.d.ts +2 -2
  47. package/dist/lib/utils/config-resolver.d.ts.map +1 -1
  48. package/dist/lib/utils/config-resolver.js +14 -7
  49. package/dist/lib/utils/config-resolver.js.map +1 -1
  50. package/dist/lib/utils/config.d.ts +1 -1
  51. package/dist/lib/utils/config.d.ts.map +1 -1
  52. package/dist/lib/utils/config.js +7 -3
  53. package/dist/lib/utils/config.js.map +1 -1
  54. package/dist/lib/utils/sqs.d.ts +13 -0
  55. package/dist/lib/utils/sqs.d.ts.map +1 -0
  56. package/dist/lib/utils/sqs.js +22 -0
  57. package/dist/lib/utils/sqs.js.map +1 -0
  58. package/dist/lib/utils/stack-inference.d.ts.map +1 -1
  59. package/dist/lib/utils/stack-inference.js +8 -6
  60. package/dist/lib/utils/stack-inference.js.map +1 -1
  61. package/dist/lib/xdg-config.d.ts +3 -1
  62. package/dist/lib/xdg-config.d.ts.map +1 -1
  63. package/dist/lib/xdg-config.js +7 -1
  64. package/dist/lib/xdg-config.js.map +1 -1
  65. package/dist/package.json +4 -2
  66. package/dist/scripts/get-dev-version.d.ts +16 -0
  67. package/dist/scripts/get-dev-version.d.ts.map +1 -0
  68. package/dist/scripts/get-dev-version.js +57 -0
  69. package/dist/scripts/get-dev-version.js.map +1 -0
  70. package/package.json +4 -2
  71. package/dist/lib/xdg-cli-wrapper.d.ts +0 -113
  72. package/dist/lib/xdg-cli-wrapper.d.ts.map +0 -1
  73. package/dist/lib/xdg-cli-wrapper.js +0 -289
  74. package/dist/lib/xdg-cli-wrapper.js.map +0 -1
  75. package/dist/scripts/infer-quilt-config.d.ts +0 -47
  76. package/dist/scripts/infer-quilt-config.d.ts.map +0 -1
  77. package/dist/scripts/infer-quilt-config.js +0 -315
  78. package/dist/scripts/infer-quilt-config.js.map +0 -1
@@ -53,32 +53,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
53
53
  Object.defineProperty(exports, "__esModule", { value: true });
54
54
  exports.setupWizardCommand = setupWizardCommand;
55
55
  const https = __importStar(require("https"));
56
- const fs_1 = require("fs");
57
- const path_1 = require("path");
58
56
  const inquirer_1 = __importDefault(require("inquirer"));
59
- const chalk_1 = __importDefault(require("chalk"));
60
- const boxen_1 = __importDefault(require("boxen"));
61
- const ora_1 = __importDefault(require("ora"));
62
57
  const client_s3_1 = require("@aws-sdk/client-s3");
63
58
  const xdg_config_1 = require("../../lib/xdg-config");
64
59
  const infer_quilt_config_1 = require("../commands/infer-quilt-config");
65
- const manifest_1 = require("./manifest");
66
- const sync_secrets_1 = require("./sync-secrets");
67
- const deploy_1 = require("./deploy");
68
- // eslint-disable-next-line @typescript-eslint/no-require-imports
69
- const pkg = require("../../package.json");
70
- const MANIFEST_FILENAME = "benchling-app-manifest.yaml";
71
- function getAccountFromStackArn(stackArn) {
72
- if (!stackArn) {
73
- return undefined;
74
- }
75
- const parts = stackArn.split(":");
76
- if (parts.length < 5) {
77
- return undefined;
78
- }
79
- const account = parts[4];
80
- return /^[0-9]{12}$/.test(account) ? account : undefined;
81
- }
60
+ const sqs_1 = require("../../lib/utils/sqs");
82
61
  // =============================================================================
83
62
  // VALIDATION FUNCTIONS (from scripts/config/validator.ts)
84
63
  // =============================================================================
@@ -285,311 +264,152 @@ async function runConfigWizard(options = {}) {
285
264
  console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
286
265
  }
287
266
  const config = { ...existingConfig };
267
+ let awsAccountId;
288
268
  // If non-interactive, validate that all required fields are present
289
269
  if (nonInteractive) {
290
270
  if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
291
271
  throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
292
272
  }
293
- return config;
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;
294
286
  }
295
287
  // Prompt for Quilt configuration (if not inherited)
296
288
  if (!inheritFrom) {
297
- // Check if all required Quilt fields are already present (from inference)
298
- // Note: bucket is optional and can be inferred from other sources if missing
299
- const hasAllQuiltFields = config.quilt?.stackArn &&
300
- config.quilt?.catalog &&
301
- config.quilt?.database &&
302
- config.quilt?.queueArn;
303
- let shouldPromptForQuilt = !hasAllQuiltFields;
304
- if (hasAllQuiltFields) {
305
- console.log("Step 1: Quilt Configuration (inferred from AWS)\n");
306
- console.log(` Stack ARN: ${config.quilt.stackArn}`);
307
- console.log(` Catalog: ${config.quilt.catalog}`);
308
- console.log(` Database: ${config.quilt.database}`);
309
- console.log(` Queue ARN: ${config.quilt.queueArn}`);
310
- if (config.quilt.bucket) {
311
- console.log(` Bucket: ${config.quilt.bucket}`);
312
- }
313
- console.log("");
314
- const { confirmQuilt } = await inquirer_1.default.prompt([
315
- {
316
- type: "confirm",
317
- name: "confirmQuilt",
318
- message: "Use these inferred Quilt settings?",
319
- default: true,
320
- },
321
- ]);
322
- if (!confirmQuilt) {
323
- console.log("\nPlease enter Quilt configuration manually:\n");
324
- shouldPromptForQuilt = true;
325
- }
326
- }
327
- // Only prompt for Quilt fields if not all are present OR user chose to enter manually
328
- if (shouldPromptForQuilt) {
329
- if (!hasAllQuiltFields) {
330
- console.log("Step 1: Quilt Configuration\n");
331
- console.log("Note: Run 'npm run setup:infer' first to auto-detect Quilt stack\n");
332
- }
333
- const quiltAnswers = await inquirer_1.default.prompt([
334
- {
335
- type: "input",
336
- name: "stackArn",
337
- message: "Quilt Stack ARN:",
338
- default: config.quilt?.stackArn,
339
- validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:cloudformation:") ||
340
- "Stack ARN is required and must start with arn:aws:cloudformation:",
341
- },
342
- {
343
- type: "input",
344
- name: "catalog",
345
- message: "Quilt Catalog URL (domain or full URL):",
346
- default: config.quilt?.catalog,
347
- validate: (input) => {
348
- const trimmed = input.trim();
349
- if (trimmed.length === 0) {
350
- return "Catalog URL is required";
351
- }
352
- return true;
353
- },
354
- filter: (input) => {
355
- // Strip protocol if present, store only domain
356
- return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
357
- },
358
- },
359
- {
360
- type: "input",
361
- name: "bucket",
362
- message: "Quilt S3 Bucket:",
363
- default: config.quilt?.bucket,
364
- validate: (input) => input.trim().length > 0 || "Bucket name is required",
365
- },
366
- {
367
- type: "input",
368
- name: "database",
369
- message: "Quilt Athena Database:",
370
- default: config.quilt?.database || "quilt_catalog",
371
- validate: (input) => input.trim().length > 0 || "Database name is required",
372
- },
373
- {
374
- type: "input",
375
- name: "queueArn",
376
- message: "SQS Queue ARN:",
377
- default: config.quilt?.queueArn,
378
- validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:sqs:") ||
379
- "Queue ARN is required and must start with arn:aws:sqs:",
380
- },
381
- ]);
382
- // Extract region from stack ARN
383
- const arnMatch = quiltAnswers.stackArn.match(/^arn:aws:cloudformation:([^:]+):/);
384
- const quiltRegion = arnMatch ? arnMatch[1] : "us-east-1";
385
- config.quilt = {
386
- stackArn: quiltAnswers.stackArn,
387
- catalog: quiltAnswers.catalog,
388
- bucket: quiltAnswers.bucket,
389
- database: quiltAnswers.database,
390
- queueArn: quiltAnswers.queueArn,
391
- region: quiltRegion,
392
- };
393
- }
394
- }
395
- // Prompt for Benchling configuration & guide app setup
396
- console.log("\nStep 2: Create Benchling App\n");
397
- let benchlingTenant = config.benchling?.tenant?.trim();
398
- benchlingTenant = benchlingTenant && benchlingTenant.length > 0 ? benchlingTenant : undefined;
399
- if (nonInteractive) {
400
- if (!benchlingTenant) {
401
- throw new Error("Benchling tenant must be provided in non-interactive mode");
402
- }
403
- }
404
- else {
405
- while (!benchlingTenant) {
406
- const tenantAnswer = await inquirer_1.default.prompt([
407
- {
408
- type: "input",
409
- name: "tenant",
410
- message: "Benchling tenant (e.g., 'acme' for acme.benchling.com):",
411
- default: config.benchling?.tenant || "",
412
- filter: (value) => value.trim(),
413
- validate: (input) => input.trim().length > 0 || "Tenant is required",
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;
414
310
  },
415
- ]);
416
- const candidateTenant = tenantAnswer.tenant.trim();
417
- const tenantValidation = await validateBenchlingTenant(candidateTenant);
418
- if (tenantValidation.isValid) {
419
- benchlingTenant = candidateTenant;
420
- if (tenantValidation.warnings && tenantValidation.warnings.length > 0) {
421
- console.warn("");
422
- console.warn("⚠ Tenant validation warnings:");
423
- tenantValidation.warnings.forEach((warning) => console.warn(` - ${warning}`));
424
- console.warn("");
425
- }
426
- }
427
- else {
428
- console.error("\n❌ Benchling tenant validation failed:");
429
- tenantValidation.errors.forEach((err) => console.error(` - ${err}`));
430
- console.log("");
431
- }
432
- }
433
- }
434
- let manifestPath = (0, path_1.join)(process.cwd(), MANIFEST_FILENAME);
435
- if (!nonInteractive) {
436
- const manifestContent = (0, manifest_1.generateBenchlingManifest)({
437
- catalogDomain: config.quilt?.catalog,
438
- version: pkg.version,
439
- });
440
- let shouldWriteManifest = true;
441
- if ((0, fs_1.existsSync)(manifestPath)) {
442
- const { overwrite } = await inquirer_1.default.prompt([
443
- {
444
- type: "confirm",
445
- name: "overwrite",
446
- message: `A manifest already exists at ${manifestPath}. Overwrite it?`,
447
- default: false,
311
+ filter: (input) => {
312
+ // Strip protocol if present, store only domain
313
+ return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
448
314
  },
449
- ]);
450
- shouldWriteManifest = overwrite;
451
- if (!overwrite) {
452
- console.log(`\nUsing existing manifest: ${manifestPath}`);
453
- }
454
- }
455
- if (shouldWriteManifest) {
456
- (0, fs_1.writeFileSync)(manifestPath, manifestContent, "utf-8");
457
- console.log(`\n✓ Generated app manifest: ${manifestPath}`);
458
- }
459
- const manifestAppName = config.quilt?.catalog && config.quilt.catalog.length > 0
460
- ? config.quilt.catalog.replace(/[.:]/g, "-")
461
- : "Quilt Integration";
462
- const instructions = chalk_1.default.bold("Create your Benchling app:\n\n") +
463
- `1. Open ${chalk_1.default.cyan(`https://${benchlingTenant}.benchling.com/admin/apps`)}\n` +
464
- "2. Click 'Create New App'\n" +
465
- `3. Upload the manifest: ${chalk_1.default.cyan(manifestPath)}\n` +
466
- "4. Create OAuth credentials and copy the Client ID / Secret\n" +
467
- "5. Install the app (leave webhook URL blank for now)\n" +
468
- "6. Copy the App Definition ID from the overview page\n";
469
- console.log();
470
- console.log((0, boxen_1.default)(instructions, {
471
- padding: 1,
472
- borderColor: "blue",
473
- borderStyle: "round",
474
- }));
475
- console.log();
476
- const { ready } = await inquirer_1.default.prompt([
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
+ },
477
323
  {
478
- type: "confirm",
479
- name: "ready",
480
- message: "Have you created and installed the Benchling app?",
481
- default: true,
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
+ },
482
332
  },
483
333
  ]);
484
- if (!ready) {
485
- console.log("\n⏸ Setup paused. Re-run when ready:\n");
486
- console.log(` ${chalk_1.default.cyan("npx @quiltdata/benchling-webhook setup")}\n`);
487
- process.exit(0);
488
- }
489
- console.log(`Using manifest app name: ${manifestAppName}\n`);
490
- }
491
- let benchlingClientId = config.benchling?.clientId?.trim();
492
- let benchlingClientSecret = config.benchling?.clientSecret?.trim();
493
- let benchlingAppDefinitionId = config.benchling?.appDefinitionId?.trim();
494
- let benchlingTestEntryId = config.benchling?.testEntryId?.trim();
495
- if (nonInteractive) {
496
- if (!benchlingClientId || !benchlingClientSecret || !benchlingAppDefinitionId) {
497
- throw new Error("Non-interactive mode requires Benchling clientId, clientSecret, and appDefinitionId to be configured");
498
- }
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
+ };
499
346
  }
500
- else {
501
- let credentialsValid = false;
502
- while (!credentialsValid) {
503
- const credentialAnswers = await inquirer_1.default.prompt([
504
- {
505
- type: "input",
506
- name: "clientId",
507
- message: "Benchling OAuth Client ID:",
508
- default: benchlingClientId || "",
509
- filter: (value) => value.trim(),
510
- validate: (input) => input.trim().length > 0 || "Client ID is required",
511
- },
512
- {
513
- type: "password",
514
- name: "clientSecret",
515
- message: benchlingClientSecret
516
- ? "Benchling OAuth Client Secret (press Enter to keep existing):"
517
- : "Benchling OAuth Client Secret:",
518
- },
519
- {
520
- type: "input",
521
- name: "appDefinitionId",
522
- message: "Benchling App Definition ID:",
523
- default: benchlingAppDefinitionId || "",
524
- filter: (value) => value.trim(),
525
- validate: (input) => input.trim().length > 0 || "App Definition ID is required",
526
- },
527
- {
528
- type: "input",
529
- name: "testEntryId",
530
- message: "Benchling Test Entry ID (optional):",
531
- default: benchlingTestEntryId || "",
532
- filter: (value) => value.trim(),
533
- },
534
- ]);
535
- const candidateSecret = credentialAnswers.clientSecret.trim().length === 0 && benchlingClientSecret
536
- ? benchlingClientSecret
537
- : credentialAnswers.clientSecret.trim();
538
- if (!candidateSecret) {
539
- console.error("\n❌ Benchling OAuth client secret is required\n");
540
- continue;
541
- }
542
- const spinner = (0, ora_1.default)("Validating Benchling credentials...").start();
543
- const validation = await validateBenchlingCredentials(benchlingTenant, credentialAnswers.clientId.trim(), candidateSecret);
544
- if (validation.isValid) {
545
- spinner.succeed("Benchling credentials validated");
546
- if (validation.warnings && validation.warnings.length > 0) {
547
- console.warn("\n⚠ Credential validation warnings:");
548
- validation.warnings.forEach((warn) => console.warn(` - ${warn}`));
549
- console.warn("");
550
- }
551
- benchlingClientId = credentialAnswers.clientId.trim();
552
- benchlingClientSecret = candidateSecret;
553
- benchlingAppDefinitionId = credentialAnswers.appDefinitionId.trim();
554
- benchlingTestEntryId = credentialAnswers.testEntryId || undefined;
555
- credentialsValid = true;
556
- }
557
- else {
558
- spinner.fail("Benchling credential validation failed");
559
- console.error("");
560
- validation.errors.forEach((err) => console.error(` - ${err}`));
561
- console.error("");
562
- const { retry } = await inquirer_1.default.prompt([
563
- {
564
- type: "confirm",
565
- name: "retry",
566
- message: "Credentials invalid. Try again?",
567
- default: true,
568
- },
569
- ]);
570
- if (!retry) {
571
- throw new Error("Setup aborted due to invalid Benchling credentials");
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
+ },
364
+ {
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;
572
374
  }
573
- }
574
- }
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;
575
395
  }
576
396
  config.benchling = {
577
- tenant: benchlingTenant,
578
- clientId: benchlingClientId,
579
- clientSecret: benchlingClientSecret,
580
- appDefinitionId: benchlingAppDefinitionId,
397
+ tenant: benchlingAnswers.tenant,
398
+ clientId: benchlingAnswers.clientId,
399
+ clientSecret: benchlingAnswers.clientSecret,
400
+ appDefinitionId: benchlingAnswers.appDefinitionId,
581
401
  };
582
- if (benchlingTestEntryId && benchlingTestEntryId.length > 0) {
583
- config.benchling.testEntryId = benchlingTestEntryId;
402
+ if (benchlingAnswers.testEntryId && benchlingAnswers.testEntryId.trim() !== "") {
403
+ config.benchling.testEntryId = benchlingAnswers.testEntryId;
584
404
  }
585
405
  // Prompt for package configuration
586
- console.log("\nPackage Storage Configuration\n");
406
+ console.log("\nStep 3: Package Configuration\n");
587
407
  const packageAnswers = await inquirer_1.default.prompt([
588
408
  {
589
409
  type: "input",
590
410
  name: "bucket",
591
411
  message: "Package S3 Bucket:",
592
- default: config.packages?.bucket || config.quilt?.bucket,
412
+ default: config.packages?.bucket,
593
413
  validate: (input) => input.trim().length > 0 || "Bucket name is required",
594
414
  },
595
415
  {
@@ -611,17 +431,8 @@ async function runConfigWizard(options = {}) {
611
431
  metadataKey: packageAnswers.metadataKey,
612
432
  };
613
433
  // Prompt for deployment configuration
614
- console.log("\nDeployment Defaults\n");
615
- const defaultAccount = config.deployment?.account || getAccountFromStackArn(config.quilt?.stackArn);
434
+ console.log("\nStep 4: Deployment Configuration\n");
616
435
  const deploymentAnswers = await inquirer_1.default.prompt([
617
- {
618
- type: "input",
619
- name: "account",
620
- message: "AWS Account ID:",
621
- default: defaultAccount || "",
622
- filter: (value) => value.trim(),
623
- validate: (input) => input.trim().length === 0 || /^[0-9]{12}$/.test(input.trim()) || "Account ID must be a 12 digit number",
624
- },
625
436
  {
626
437
  type: "input",
627
438
  name: "region",
@@ -630,18 +441,26 @@ async function runConfigWizard(options = {}) {
630
441
  },
631
442
  {
632
443
  type: "input",
633
- name: "imageTag",
634
- message: "Docker image tag:",
635
- default: config.deployment?.imageTag || "latest",
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
+ },
636
456
  },
637
457
  ]);
638
458
  config.deployment = {
639
- ...(deploymentAnswers.account ? { account: deploymentAnswers.account } : {}),
640
459
  region: deploymentAnswers.region,
641
- imageTag: deploymentAnswers.imageTag,
460
+ account: deploymentAnswers.account,
642
461
  };
643
462
  // Optional: Logging configuration
644
- console.log("\nSecurity and Logging Options\n");
463
+ console.log("\nStep 5: Optional Configuration\n");
645
464
  const optionalAnswers = await inquirer_1.default.prompt([
646
465
  {
647
466
  type: "list",
@@ -650,12 +469,6 @@ async function runConfigWizard(options = {}) {
650
469
  choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
651
470
  default: config.logging?.level || "INFO",
652
471
  },
653
- {
654
- type: "confirm",
655
- name: "enableVerification",
656
- message: "Enable webhook signature verification:",
657
- default: config.security?.enableVerification !== false,
658
- },
659
472
  {
660
473
  type: "input",
661
474
  name: "webhookAllowList",
@@ -667,13 +480,13 @@ async function runConfigWizard(options = {}) {
667
480
  level: optionalAnswers.logLevel,
668
481
  };
669
482
  config.security = {
670
- enableVerification: optionalAnswers.enableVerification,
483
+ enableVerification: true,
671
484
  webhookAllowList: optionalAnswers.webhookAllowList,
672
485
  };
673
486
  // Add metadata
674
487
  const now = new Date().toISOString();
675
488
  config._metadata = {
676
- version: pkg.version,
489
+ version: "0.7.0",
677
490
  createdAt: config._metadata?.createdAt || now,
678
491
  updatedAt: now,
679
492
  source: "wizard",
@@ -695,37 +508,41 @@ async function runConfigWizard(options = {}) {
695
508
  * 5. Save to XDG config directory
696
509
  */
697
510
  async function runInstallWizard(options = {}) {
698
- const { profile = "default", inheritFrom, nonInteractive = false, skipValidation = false, skipSecretsSync = false, skipDeployment = false, deployStage = "prod", awsProfile, awsRegion = "us-east-1", } = options;
511
+ const { profile = "default", inheritFrom, nonInteractive = false, skipValidation = false, awsProfile, awsRegion = "us-east-1", } = options;
699
512
  const xdg = new xdg_config_1.XDGConfig();
700
513
  console.log("\n╔═══════════════════════════════════════════════════════════╗");
701
- const headerLine = `║ Benchling Webhook Setup (v${pkg.version})`;
702
- console.log(`${headerLine.padEnd(59, " ")}║`);
514
+ console.log("║ Benchling Webhook Setup (v0.7.0) ║");
703
515
  console.log("╚═══════════════════════════════════════════════════════════╝\n");
704
516
  // Step 1: Load existing configuration (if profile exists)
705
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);
706
521
  if (xdg.profileExists(profile)) {
707
522
  console.log(`Loading existing configuration for profile: ${profile}\n`);
708
523
  try {
709
- existingConfig = inheritFrom
710
- ? xdg.readProfileWithInheritance(profile, inheritFrom)
524
+ existingConfig = effectiveInheritFrom
525
+ ? xdg.readProfileWithInheritance(profile, effectiveInheritFrom)
711
526
  : xdg.readProfile(profile);
712
527
  }
713
528
  catch (error) {
714
529
  console.warn(`Warning: Could not load existing config: ${error.message}`);
715
530
  }
716
531
  }
717
- else if (inheritFrom) {
718
- console.log(`Creating new profile '${profile}' inheriting from '${inheritFrom}'\n`);
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`);
719
535
  try {
720
- existingConfig = xdg.readProfile(inheritFrom);
536
+ existingConfig = xdg.readProfile(effectiveInheritFrom);
721
537
  }
722
538
  catch (error) {
723
- throw new Error(`Base profile '${inheritFrom}' not found: ${error.message}`);
539
+ throw new Error(`Base profile '${effectiveInheritFrom}' not found: ${error.message}`);
724
540
  }
725
541
  }
726
542
  // Step 2: Infer Quilt configuration (unless inheriting from another profile)
727
543
  let quiltConfig = existingConfig?.quilt || {};
728
- if (!inheritFrom || !existingConfig?.quilt) {
544
+ let inferredAccountId;
545
+ if (!effectiveInheritFrom || !existingConfig?.quilt) {
729
546
  console.log("Step 1: Inferring Quilt configuration from AWS...\n");
730
547
  try {
731
548
  const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
@@ -733,29 +550,8 @@ async function runInstallWizard(options = {}) {
733
550
  profile: awsProfile,
734
551
  interactive: !nonInteractive,
735
552
  });
736
- // Map InferenceResult to QuiltConfig fields
737
- if (inferenceResult.quiltStackArn) {
738
- quiltConfig.stackArn = inferenceResult.quiltStackArn;
739
- }
740
- if (inferenceResult.catalogUrl) {
741
- // Strip protocol and trailing slash to store only domain
742
- quiltConfig.catalog = inferenceResult.catalogUrl.replace(/^https?:\/\//, "").replace(/\/$/, "");
743
- }
744
- if (inferenceResult.quiltUserBucket) {
745
- quiltConfig.bucket = inferenceResult.quiltUserBucket;
746
- }
747
- if (inferenceResult.quiltDatabase) {
748
- quiltConfig.database = inferenceResult.quiltDatabase;
749
- }
750
- if (inferenceResult.queueArn) {
751
- const queueArn = inferenceResult.queueArn;
752
- if (queueArn.startsWith("arn:aws:sqs:") || !quiltConfig.queueArn) {
753
- quiltConfig.queueArn = queueArn;
754
- }
755
- }
756
- if (inferenceResult.quiltRegion) {
757
- quiltConfig.region = inferenceResult.quiltRegion;
758
- }
553
+ quiltConfig = inferenceResult;
554
+ inferredAccountId = inferenceResult.account;
759
555
  console.log("✓ Quilt configuration inferred\n");
760
556
  }
761
557
  catch (error) {
@@ -783,12 +579,17 @@ async function runInstallWizard(options = {}) {
783
579
  ...existingConfig?.quilt,
784
580
  ...quiltConfig,
785
581
  },
582
+ // Pass through inferred account ID for deployment config
583
+ deployment: {
584
+ ...existingConfig?.deployment,
585
+ account: existingConfig?.deployment?.account || inferredAccountId,
586
+ },
786
587
  };
787
588
  // Step 3: Run interactive wizard for remaining configuration
788
- let config = await runConfigWizard({
589
+ const config = await runConfigWizard({
789
590
  existingConfig: partialConfig,
790
591
  nonInteractive,
791
- inheritFrom,
592
+ inheritFrom: effectiveInheritFrom,
792
593
  });
793
594
  // Step 4: Validate configuration
794
595
  if (!skipValidation) {
@@ -824,66 +625,23 @@ async function runInstallWizard(options = {}) {
824
625
  console.log("");
825
626
  }
826
627
  }
827
- // Step 5: Persist configuration locally
628
+ // Step 5: Save configuration
828
629
  console.log(`Saving configuration to profile: ${profile}...\n`);
829
630
  try {
830
631
  xdg.writeProfile(profile, config);
831
- console.log(chalk_1.default.green(`✓ Configuration saved: ~/.config/benchling-webhook/${profile}/config.json\n`));
632
+ console.log(`✓ Configuration saved to: ~/.config/benchling-webhook/${profile}/config.json\n`);
832
633
  }
833
634
  catch (error) {
834
635
  throw new Error(`Failed to save configuration: ${error.message}`);
835
636
  }
836
- // Step 6: Sync secrets and deploy (unless skipped)
837
- console.log(chalk_1.default.bold("Step 3: Deploy Stack and Return Webhook URL\n"));
838
- if (skipSecretsSync) {
839
- console.log(chalk_1.default.yellow("Skipping secrets sync (--skip-secrets-sync)."));
840
- console.log(chalk_1.default.yellow(`Run \`npm run setup:sync-secrets -- --profile ${profile}\` when ready.\n`));
841
- }
842
- else {
843
- console.log("Syncing secrets to AWS Secrets Manager...\n");
844
- try {
845
- await (0, sync_secrets_1.syncSecretsToAWS)({
846
- profile,
847
- awsProfile,
848
- region: config.deployment.region,
849
- force: true,
850
- });
851
- console.log(chalk_1.default.green("\n✓ Secrets synced to AWS Secrets Manager\n"));
852
- // Reload config to capture secret ARN written by sync
853
- config = xdg.readProfile(profile);
854
- }
855
- catch (error) {
856
- throw new Error(`Secrets sync failed: ${error.message}`);
857
- }
858
- }
859
- if (skipDeployment) {
860
- console.log(chalk_1.default.yellow("Skipping deployment (--skip-deployment)."));
861
- console.log(chalk_1.default.yellow(`Run \`npm run deploy -- --profile ${profile} --stage ${deployStage}\` to deploy later.\n`));
862
- return config;
863
- }
864
- if (!nonInteractive) {
865
- console.log(`Deploy target: profile=${chalk_1.default.cyan(profile)}, stage=${chalk_1.default.cyan(deployStage)}, region=${chalk_1.default.cyan(config.deployment.region)}\n`);
866
- const { confirmDeploy } = await inquirer_1.default.prompt([
867
- {
868
- type: "confirm",
869
- name: "confirmDeploy",
870
- message: "Deploy AWS infrastructure now? (takes ~5-10 minutes)",
871
- default: true,
872
- },
873
- ]);
874
- if (!confirmDeploy) {
875
- console.log(chalk_1.default.yellow("\nDeployment skipped by user."));
876
- console.log(chalk_1.default.yellow(`Re-run with \`npm run deploy -- --profile ${profile} --stage ${deployStage}\` when ready.\n`));
877
- return config;
878
- }
879
- }
880
- console.log("Deploying AWS infrastructure. This can take several minutes...\n");
881
- await (0, deploy_1.deployCommand)({
882
- profile,
883
- stage: deployStage,
884
- requireApproval: "never",
885
- yes: true,
886
- });
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");
887
645
  return config;
888
646
  }
889
647
  // =============================================================================