@quiltdata/benchling-webhook 0.7.2-20251106T010445Z → 0.7.3-20251106T131511Z

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 (94) hide show
  1. package/README.md +33 -159
  2. package/dist/bin/benchling-webhook.d.ts +0 -1
  3. package/dist/bin/benchling-webhook.d.ts.map +1 -1
  4. package/dist/bin/benchling-webhook.js +2 -7
  5. package/dist/bin/benchling-webhook.js.map +1 -1
  6. package/dist/bin/cli.js +17 -12
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/bin/commands/config-profiles.d.ts.map +1 -1
  9. package/dist/bin/commands/config-profiles.js +1 -2
  10. package/dist/bin/commands/config-profiles.js.map +1 -1
  11. package/dist/bin/commands/deploy.d.ts +1 -1
  12. package/dist/bin/commands/deploy.d.ts.map +1 -1
  13. package/dist/bin/commands/deploy.js +83 -10
  14. package/dist/bin/commands/deploy.js.map +1 -1
  15. package/dist/bin/commands/infer-quilt-config.d.ts +6 -15
  16. package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -1
  17. package/dist/bin/commands/infer-quilt-config.js +96 -69
  18. package/dist/bin/commands/infer-quilt-config.js.map +1 -1
  19. package/dist/bin/commands/init.d.ts +5 -1
  20. package/dist/bin/commands/init.d.ts.map +1 -1
  21. package/dist/bin/commands/init.js +19 -111
  22. package/dist/bin/commands/init.js.map +1 -1
  23. package/dist/bin/commands/manifest.d.ts +0 -11
  24. package/dist/bin/commands/manifest.d.ts.map +1 -1
  25. package/dist/bin/commands/manifest.js +8 -22
  26. package/dist/bin/commands/manifest.js.map +1 -1
  27. package/dist/bin/commands/setup-profile.d.ts.map +1 -1
  28. package/dist/bin/commands/setup-profile.js +15 -17
  29. package/dist/bin/commands/setup-profile.js.map +1 -1
  30. package/dist/bin/commands/setup-wizard.d.ts +0 -2
  31. package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
  32. package/dist/bin/commands/setup-wizard.js +227 -405
  33. package/dist/bin/commands/setup-wizard.js.map +1 -1
  34. package/dist/bin/commands/sync-secrets.js +2 -2
  35. package/dist/lib/benchling-webhook-stack.d.ts +0 -6
  36. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  37. package/dist/lib/benchling-webhook-stack.js +21 -20
  38. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  39. package/dist/lib/configuration-saver.d.ts +0 -8
  40. package/dist/lib/configuration-saver.d.ts.map +1 -1
  41. package/dist/lib/configuration-saver.js +1 -18
  42. package/dist/lib/configuration-saver.js.map +1 -1
  43. package/dist/lib/fargate-service.d.ts +4 -2
  44. package/dist/lib/fargate-service.d.ts.map +1 -1
  45. package/dist/lib/fargate-service.js +25 -12
  46. package/dist/lib/fargate-service.js.map +1 -1
  47. package/dist/lib/types/config.d.ts +9 -70
  48. package/dist/lib/types/config.d.ts.map +1 -1
  49. package/dist/lib/types/config.js +2 -3
  50. package/dist/lib/types/config.js.map +1 -1
  51. package/dist/lib/utils/config-loader.d.ts.map +1 -1
  52. package/dist/lib/utils/config-loader.js +3 -2
  53. package/dist/lib/utils/config-loader.js.map +1 -1
  54. package/dist/lib/utils/config-resolver.d.ts +2 -2
  55. package/dist/lib/utils/config-resolver.d.ts.map +1 -1
  56. package/dist/lib/utils/config-resolver.js +14 -7
  57. package/dist/lib/utils/config-resolver.js.map +1 -1
  58. package/dist/lib/utils/config.d.ts +1 -1
  59. package/dist/lib/utils/config.d.ts.map +1 -1
  60. package/dist/lib/utils/config.js +7 -3
  61. package/dist/lib/utils/config.js.map +1 -1
  62. package/dist/lib/utils/sqs.d.ts +13 -0
  63. package/dist/lib/utils/sqs.d.ts.map +1 -0
  64. package/dist/lib/utils/sqs.js +22 -0
  65. package/dist/lib/utils/sqs.js.map +1 -0
  66. package/dist/lib/utils/stack-inference.d.ts.map +1 -1
  67. package/dist/lib/utils/stack-inference.js +8 -6
  68. package/dist/lib/utils/stack-inference.js.map +1 -1
  69. package/dist/lib/xdg-config.d.ts +3 -1
  70. package/dist/lib/xdg-config.d.ts.map +1 -1
  71. package/dist/lib/xdg-config.js +7 -1
  72. package/dist/lib/xdg-config.js.map +1 -1
  73. package/dist/package.json +7 -4
  74. package/dist/scripts/check-logs.d.ts +1 -1
  75. package/dist/scripts/check-logs.d.ts.map +1 -1
  76. package/dist/scripts/check-logs.js +10 -4
  77. package/dist/scripts/check-logs.js.map +1 -1
  78. package/dist/scripts/get-dev-version.d.ts +16 -0
  79. package/dist/scripts/get-dev-version.d.ts.map +1 -0
  80. package/dist/scripts/get-dev-version.js +57 -0
  81. package/dist/scripts/get-dev-version.js.map +1 -0
  82. package/dist/scripts/send-event.d.ts +1 -1
  83. package/dist/scripts/send-event.d.ts.map +1 -1
  84. package/dist/scripts/send-event.js +4 -5
  85. package/dist/scripts/send-event.js.map +1 -1
  86. package/package.json +7 -4
  87. package/dist/lib/xdg-cli-wrapper.d.ts +0 -113
  88. package/dist/lib/xdg-cli-wrapper.d.ts.map +0 -1
  89. package/dist/lib/xdg-cli-wrapper.js +0 -289
  90. package/dist/lib/xdg-cli-wrapper.js.map +0 -1
  91. package/dist/scripts/infer-quilt-config.d.ts +0 -47
  92. package/dist/scripts/infer-quilt-config.d.ts.map +0 -1
  93. package/dist/scripts/infer-quilt-config.js +0 -315
  94. package/dist/scripts/infer-quilt-config.js.map +0 -1
@@ -53,32 +53,13 @@ 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
57
  const chalk_1 = __importDefault(require("chalk"));
60
- const boxen_1 = __importDefault(require("boxen"));
61
- const ora_1 = __importDefault(require("ora"));
62
58
  const client_s3_1 = require("@aws-sdk/client-s3");
63
59
  const xdg_config_1 = require("../../lib/xdg-config");
64
60
  const infer_quilt_config_1 = require("../commands/infer-quilt-config");
61
+ const sqs_1 = require("../../lib/utils/sqs");
65
62
  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
- }
82
63
  // =============================================================================
83
64
  // VALIDATION FUNCTIONS (from scripts/config/validator.ts)
84
65
  // =============================================================================
@@ -285,311 +266,195 @@ async function runConfigWizard(options = {}) {
285
266
  console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
286
267
  }
287
268
  const config = { ...existingConfig };
269
+ let awsAccountId;
288
270
  // If non-interactive, validate that all required fields are present
289
271
  if (nonInteractive) {
290
272
  if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
291
273
  throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
292
274
  }
293
- return config;
275
+ // Add metadata and inheritance marker before returning
276
+ const now = new Date().toISOString();
277
+ const finalConfig = config;
278
+ finalConfig._metadata = {
279
+ version: "0.7.0",
280
+ createdAt: config._metadata?.createdAt || now,
281
+ updatedAt: now,
282
+ source: "wizard",
283
+ };
284
+ if (inheritFrom) {
285
+ finalConfig._inherits = inheritFrom;
286
+ }
287
+ return finalConfig;
294
288
  }
295
289
  // Prompt for Quilt configuration (if not inherited)
296
290
  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",
291
+ console.log("Step 1: Quilt Configuration\n");
292
+ const quiltAnswers = await inquirer_1.default.prompt([
293
+ {
294
+ type: "input",
295
+ name: "stackArn",
296
+ message: "Quilt Stack ARN:",
297
+ default: config.quilt?.stackArn,
298
+ validate: (input) => input.trim().length > 0 && input.startsWith("arn:aws:cloudformation:") ||
299
+ "Stack ARN is required and must start with arn:aws:cloudformation:",
300
+ },
301
+ {
302
+ type: "input",
303
+ name: "catalog",
304
+ message: "Quilt Catalog URL (domain or full URL):",
305
+ default: config.quilt?.catalog,
306
+ validate: (input) => {
307
+ const trimmed = input.trim();
308
+ if (trimmed.length === 0) {
309
+ return "Catalog URL is required";
310
+ }
311
+ return true;
365
312
  },
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",
313
+ filter: (input) => {
314
+ // Strip protocol if present, store only domain
315
+ return input.trim().replace(/^https?:\/\//, "").replace(/\/$/, "");
372
316
  },
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:",
317
+ },
318
+ {
319
+ type: "input",
320
+ name: "database",
321
+ message: "Quilt Athena Database:",
322
+ default: config.quilt?.database || "quilt_catalog",
323
+ validate: (input) => input.trim().length > 0 || "Database name is required",
324
+ },
325
+ {
326
+ type: "input",
327
+ name: "queueUrl",
328
+ message: "SQS Queue URL:",
329
+ default: config.quilt?.queueUrl,
330
+ validate: (input) => {
331
+ return (0, sqs_1.isQueueUrl)(input) ||
332
+ "Queue URL is required and must look like https://sqs.<region>.amazonaws.com/<account>/<queue>";
380
333
  },
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
- }
334
+ },
335
+ ]);
336
+ // Extract region and account ID from stack ARN
337
+ // ARN format: arn:aws:cloudformation:REGION:ACCOUNT_ID:stack/STACK_NAME/STACK_ID
338
+ const arnMatch = quiltAnswers.stackArn.match(/^arn:aws:cloudformation:([^:]+):(\d{12}):/);
339
+ const quiltRegion = arnMatch ? arnMatch[1] : "us-east-1";
340
+ awsAccountId = arnMatch ? arnMatch[2] : undefined;
341
+ config.quilt = {
342
+ stackArn: quiltAnswers.stackArn,
343
+ catalog: quiltAnswers.catalog,
344
+ database: quiltAnswers.database,
345
+ queueUrl: quiltAnswers.queueUrl,
346
+ region: quiltRegion,
347
+ };
394
348
  }
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
- }
349
+ // Prompt for Benchling configuration
350
+ console.log("\nStep 2: Benchling Configuration\n");
351
+ // First, get tenant
352
+ const tenantAnswer = await inquirer_1.default.prompt([
353
+ {
354
+ type: "input",
355
+ name: "tenant",
356
+ message: "Benchling Tenant:",
357
+ default: config.benchling?.tenant,
358
+ validate: (input) => input.trim().length > 0 || "Tenant is required",
359
+ },
360
+ ]);
361
+ // Ask if they have an app_definition_id BEFORE asking for credentials
362
+ const hasAppDefId = await inquirer_1.default.prompt([
363
+ {
364
+ type: "confirm",
365
+ name: "hasIt",
366
+ message: "Do you have a Benchling App Definition ID for this app?",
367
+ default: !!config.benchling?.appDefinitionId,
368
+ },
369
+ ]);
370
+ let appDefinitionId;
371
+ if (hasAppDefId.hasIt) {
372
+ // They have it, ask for it
373
+ const appDefAnswer = await inquirer_1.default.prompt([
374
+ {
375
+ type: "input",
376
+ name: "appDefinitionId",
377
+ message: "Benchling App Definition ID:",
378
+ default: config.benchling?.appDefinitionId,
379
+ validate: (input) => input.trim().length > 0 || "App definition ID is required",
380
+ },
381
+ ]);
382
+ appDefinitionId = appDefAnswer.appDefinitionId;
403
383
  }
404
384
  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",
414
- },
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,
385
+ // They don't have it, create the manifest and show instructions
386
+ console.log("\n" + chalk_1.default.blue("Creating app manifest...") + "\n");
387
+ // Create manifest using the existing command
388
+ await (0, manifest_1.manifestCommand)({
389
+ catalog: config.quilt?.catalog,
390
+ output: "app-manifest.yaml",
439
391
  });
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,
448
- },
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([
392
+ console.log("\n" + chalk_1.default.yellow("After you have installed the app in Benchling and have the App Definition ID, you can continue.") + "\n");
393
+ // Now ask for the app definition ID
394
+ const appDefAnswer = await inquirer_1.default.prompt([
477
395
  {
478
- type: "confirm",
479
- name: "ready",
480
- message: "Have you created and installed the Benchling app?",
481
- default: true,
396
+ type: "input",
397
+ name: "appDefinitionId",
398
+ message: "Benchling App Definition ID:",
399
+ validate: (input) => input.trim().length > 0 || "App definition ID is required",
482
400
  },
483
401
  ]);
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`);
402
+ appDefinitionId = appDefAnswer.appDefinitionId;
490
403
  }
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
- }
499
- }
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");
404
+ // Now ask for OAuth credentials (which must come from the app)
405
+ const credentialAnswers = await inquirer_1.default.prompt([
406
+ {
407
+ type: "input",
408
+ name: "clientId",
409
+ message: "Benchling OAuth Client ID (from the app above):",
410
+ default: config.benchling?.clientId,
411
+ validate: (input) => input.trim().length > 0 || "Client ID is required",
412
+ },
413
+ {
414
+ type: "password",
415
+ name: "clientSecret",
416
+ message: config.benchling?.clientSecret
417
+ ? "Benchling OAuth Client Secret (press Enter to keep existing):"
418
+ : "Benchling OAuth Client Secret (from the app above):",
419
+ validate: (input) => {
420
+ // If there's an existing secret and input is empty, we'll keep the existing one
421
+ if (config.benchling?.clientSecret && input.trim().length === 0) {
422
+ return true;
572
423
  }
573
- }
574
- }
424
+ return input.trim().length > 0 || "Client secret is required";
425
+ },
426
+ },
427
+ ]);
428
+ // Ask for optional test entry ID
429
+ const testEntryAnswer = await inquirer_1.default.prompt([
430
+ {
431
+ type: "input",
432
+ name: "testEntryId",
433
+ message: "Benchling Test Entry ID (optional):",
434
+ default: config.benchling?.testEntryId || "",
435
+ },
436
+ ]);
437
+ // Handle empty password input - keep existing secret if user pressed Enter
438
+ if (credentialAnswers.clientSecret.trim().length === 0 && config.benchling?.clientSecret) {
439
+ credentialAnswers.clientSecret = config.benchling.clientSecret;
575
440
  }
576
441
  config.benchling = {
577
- tenant: benchlingTenant,
578
- clientId: benchlingClientId,
579
- clientSecret: benchlingClientSecret,
580
- appDefinitionId: benchlingAppDefinitionId,
442
+ tenant: tenantAnswer.tenant,
443
+ clientId: credentialAnswers.clientId,
444
+ clientSecret: credentialAnswers.clientSecret,
445
+ appDefinitionId: appDefinitionId,
581
446
  };
582
- if (benchlingTestEntryId && benchlingTestEntryId.length > 0) {
583
- config.benchling.testEntryId = benchlingTestEntryId;
447
+ if (testEntryAnswer.testEntryId && testEntryAnswer.testEntryId.trim() !== "") {
448
+ config.benchling.testEntryId = testEntryAnswer.testEntryId;
584
449
  }
585
450
  // Prompt for package configuration
586
- console.log("\nPackage Storage Configuration\n");
451
+ console.log("\nStep 3: Package Configuration\n");
587
452
  const packageAnswers = await inquirer_1.default.prompt([
588
453
  {
589
454
  type: "input",
590
455
  name: "bucket",
591
456
  message: "Package S3 Bucket:",
592
- default: config.packages?.bucket || config.quilt?.bucket,
457
+ default: config.packages?.bucket,
593
458
  validate: (input) => input.trim().length > 0 || "Bucket name is required",
594
459
  },
595
460
  {
@@ -611,17 +476,8 @@ async function runConfigWizard(options = {}) {
611
476
  metadataKey: packageAnswers.metadataKey,
612
477
  };
613
478
  // Prompt for deployment configuration
614
- console.log("\nDeployment Defaults\n");
615
- const defaultAccount = config.deployment?.account || getAccountFromStackArn(config.quilt?.stackArn);
479
+ console.log("\nStep 4: Deployment Configuration\n");
616
480
  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
481
  {
626
482
  type: "input",
627
483
  name: "region",
@@ -630,18 +486,26 @@ async function runConfigWizard(options = {}) {
630
486
  },
631
487
  {
632
488
  type: "input",
633
- name: "imageTag",
634
- message: "Docker image tag:",
635
- default: config.deployment?.imageTag || "latest",
489
+ name: "account",
490
+ message: "AWS Account ID:",
491
+ default: config.deployment?.account || awsAccountId || config.quilt?.stackArn?.match(/:(\d{12}):/)?.[1],
492
+ validate: (input) => {
493
+ if (!input || input.trim().length === 0) {
494
+ return "AWS Account ID is required";
495
+ }
496
+ if (!/^\d{12}$/.test(input.trim())) {
497
+ return "AWS Account ID must be a 12-digit number";
498
+ }
499
+ return true;
500
+ },
636
501
  },
637
502
  ]);
638
503
  config.deployment = {
639
- ...(deploymentAnswers.account ? { account: deploymentAnswers.account } : {}),
640
504
  region: deploymentAnswers.region,
641
- imageTag: deploymentAnswers.imageTag,
505
+ account: deploymentAnswers.account,
642
506
  };
643
507
  // Optional: Logging configuration
644
- console.log("\nSecurity and Logging Options\n");
508
+ console.log("\nStep 5: Optional Configuration\n");
645
509
  const optionalAnswers = await inquirer_1.default.prompt([
646
510
  {
647
511
  type: "list",
@@ -650,12 +514,6 @@ async function runConfigWizard(options = {}) {
650
514
  choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
651
515
  default: config.logging?.level || "INFO",
652
516
  },
653
- {
654
- type: "confirm",
655
- name: "enableVerification",
656
- message: "Enable webhook signature verification:",
657
- default: config.security?.enableVerification !== false,
658
- },
659
517
  {
660
518
  type: "input",
661
519
  name: "webhookAllowList",
@@ -667,13 +525,13 @@ async function runConfigWizard(options = {}) {
667
525
  level: optionalAnswers.logLevel,
668
526
  };
669
527
  config.security = {
670
- enableVerification: optionalAnswers.enableVerification,
528
+ enableVerification: true,
671
529
  webhookAllowList: optionalAnswers.webhookAllowList,
672
530
  };
673
531
  // Add metadata
674
532
  const now = new Date().toISOString();
675
533
  config._metadata = {
676
- version: pkg.version,
534
+ version: "0.7.0",
677
535
  createdAt: config._metadata?.createdAt || now,
678
536
  updatedAt: now,
679
537
  source: "wizard",
@@ -695,37 +553,41 @@ async function runConfigWizard(options = {}) {
695
553
  * 5. Save to XDG config directory
696
554
  */
697
555
  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;
556
+ const { profile = "default", inheritFrom, nonInteractive = false, skipValidation = false, awsProfile, awsRegion = "us-east-1", } = options;
699
557
  const xdg = new xdg_config_1.XDGConfig();
700
558
  console.log("\n╔═══════════════════════════════════════════════════════════╗");
701
- const headerLine = `║ Benchling Webhook Setup (v${pkg.version})`;
702
- console.log(`${headerLine.padEnd(59, " ")}║`);
559
+ console.log("║ Benchling Webhook Setup (v0.7.0) ║");
703
560
  console.log("╚═══════════════════════════════════════════════════════════╝\n");
704
561
  // Step 1: Load existing configuration (if profile exists)
705
562
  let existingConfig;
563
+ // Determine if we should inherit from 'default' when profile is not 'default'
564
+ const shouldInheritFromDefault = profile !== "default" && !inheritFrom;
565
+ const effectiveInheritFrom = inheritFrom || (shouldInheritFromDefault ? "default" : undefined);
706
566
  if (xdg.profileExists(profile)) {
707
567
  console.log(`Loading existing configuration for profile: ${profile}\n`);
708
568
  try {
709
- existingConfig = inheritFrom
710
- ? xdg.readProfileWithInheritance(profile, inheritFrom)
569
+ existingConfig = effectiveInheritFrom
570
+ ? xdg.readProfileWithInheritance(profile, effectiveInheritFrom)
711
571
  : xdg.readProfile(profile);
712
572
  }
713
573
  catch (error) {
714
574
  console.warn(`Warning: Could not load existing config: ${error.message}`);
715
575
  }
716
576
  }
717
- else if (inheritFrom) {
718
- console.log(`Creating new profile '${profile}' inheriting from '${inheritFrom}'\n`);
577
+ else if (effectiveInheritFrom) {
578
+ // If profile doesn't exist but we should inherit, load base profile
579
+ console.log(`Creating new profile '${profile}' inheriting from '${effectiveInheritFrom}'\n`);
719
580
  try {
720
- existingConfig = xdg.readProfile(inheritFrom);
581
+ existingConfig = xdg.readProfile(effectiveInheritFrom);
721
582
  }
722
583
  catch (error) {
723
- throw new Error(`Base profile '${inheritFrom}' not found: ${error.message}`);
584
+ throw new Error(`Base profile '${effectiveInheritFrom}' not found: ${error.message}`);
724
585
  }
725
586
  }
726
587
  // Step 2: Infer Quilt configuration (unless inheriting from another profile)
727
588
  let quiltConfig = existingConfig?.quilt || {};
728
- if (!inheritFrom || !existingConfig?.quilt) {
589
+ let inferredAccountId;
590
+ if (!effectiveInheritFrom || !existingConfig?.quilt) {
729
591
  console.log("Step 1: Inferring Quilt configuration from AWS...\n");
730
592
  try {
731
593
  const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
@@ -733,29 +595,8 @@ async function runInstallWizard(options = {}) {
733
595
  profile: awsProfile,
734
596
  interactive: !nonInteractive,
735
597
  });
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
- }
598
+ quiltConfig = inferenceResult;
599
+ inferredAccountId = inferenceResult.account;
759
600
  console.log("✓ Quilt configuration inferred\n");
760
601
  }
761
602
  catch (error) {
@@ -783,12 +624,17 @@ async function runInstallWizard(options = {}) {
783
624
  ...existingConfig?.quilt,
784
625
  ...quiltConfig,
785
626
  },
627
+ // Pass through inferred account ID for deployment config
628
+ deployment: {
629
+ ...existingConfig?.deployment,
630
+ account: existingConfig?.deployment?.account || inferredAccountId,
631
+ },
786
632
  };
787
633
  // Step 3: Run interactive wizard for remaining configuration
788
- let config = await runConfigWizard({
634
+ const config = await runConfigWizard({
789
635
  existingConfig: partialConfig,
790
636
  nonInteractive,
791
- inheritFrom,
637
+ inheritFrom: effectiveInheritFrom,
792
638
  });
793
639
  // Step 4: Validate configuration
794
640
  if (!skipValidation) {
@@ -824,66 +670,28 @@ async function runInstallWizard(options = {}) {
824
670
  console.log("");
825
671
  }
826
672
  }
827
- // Step 5: Persist configuration locally
673
+ // Step 5: Save configuration
828
674
  console.log(`Saving configuration to profile: ${profile}...\n`);
829
675
  try {
830
676
  xdg.writeProfile(profile, config);
831
- console.log(chalk_1.default.green(`✓ Configuration saved: ~/.config/benchling-webhook/${profile}/config.json\n`));
677
+ console.log(`✓ Configuration saved to: ~/.config/benchling-webhook/${profile}/config.json\n`);
832
678
  }
833
679
  catch (error) {
834
680
  throw new Error(`Failed to save configuration: ${error.message}`);
835
681
  }
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`));
682
+ // Step 6: Display next steps
683
+ console.log("╔═══════════════════════════════════════════════════════════╗");
684
+ console.log("║ Setup Complete! ║");
685
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
686
+ console.log("Next steps:");
687
+ if (profile === "default") {
688
+ console.log(" 1. Deploy to AWS: npm run deploy");
689
+ console.log(" 2. Test integration: npm run test\n");
841
690
  }
842
691
  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
- }
692
+ console.log(` 1. Deploy to AWS: npx benchling-webhook deploy --profile ${profile} --stage ${profile}`);
693
+ console.log(` 2. Test integration: npm run test:${profile}\n`);
879
694
  }
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
- });
887
695
  return config;
888
696
  }
889
697
  // =============================================================================
@@ -896,6 +704,20 @@ async function runInstallWizard(options = {}) {
896
704
  * @returns Promise that resolves when wizard completes
897
705
  */
898
706
  async function setupWizardCommand(options = {}) {
899
- await runInstallWizard(options);
707
+ try {
708
+ await runInstallWizard(options);
709
+ }
710
+ catch (error) {
711
+ // Handle user cancellation (Ctrl+C) gracefully
712
+ const err = error;
713
+ if (err &&
714
+ (err.message?.includes("User force closed") ||
715
+ err.message?.includes("ERR_USE_AFTER_CLOSE") ||
716
+ err.code === "ERR_USE_AFTER_CLOSE")) {
717
+ console.log(chalk_1.default.yellow("\n✖ Setup cancelled by user"));
718
+ process.exit(0);
719
+ }
720
+ throw error;
721
+ }
900
722
  }
901
723
  //# sourceMappingURL=setup-wizard.js.map