@quiltdata/benchling-webhook 0.6.3-20251104T182406Z → 0.7.2-20251106T003353Z

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 (141) 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 +98 -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} +110 -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 +11 -0
  15. package/dist/bin/commands/deploy.d.ts.map +1 -1
  16. package/dist/bin/commands/deploy.js +64 -109
  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/bin/commands/infer-quilt-config.d.ts +50 -0
  27. package/dist/bin/commands/infer-quilt-config.d.ts.map +1 -0
  28. package/dist/bin/commands/infer-quilt-config.js +356 -0
  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/manifest.d.ts +11 -0
  34. package/dist/bin/commands/manifest.d.ts.map +1 -1
  35. package/dist/bin/commands/manifest.js +22 -8
  36. package/dist/bin/commands/manifest.js.map +1 -1
  37. package/dist/bin/commands/publish.d.ts.map +1 -0
  38. package/dist/bin/{publish.js → commands/publish.js} +2 -2
  39. package/dist/bin/commands/publish.js.map +1 -0
  40. package/dist/bin/commands/setup-profile.d.ts +29 -0
  41. package/dist/bin/commands/setup-profile.d.ts.map +1 -0
  42. package/dist/bin/commands/setup-profile.js +220 -0
  43. package/dist/bin/commands/setup-profile.js.map +1 -0
  44. package/dist/bin/commands/setup-wizard.d.ts +26 -11
  45. package/dist/bin/commands/setup-wizard.d.ts.map +1 -1
  46. package/dist/bin/commands/setup-wizard.js +844 -46
  47. package/dist/bin/commands/setup-wizard.js.map +1 -1
  48. package/dist/{scripts → bin/commands}/sync-secrets.d.ts +6 -1
  49. package/dist/bin/commands/sync-secrets.d.ts.map +1 -0
  50. package/dist/{scripts → bin/commands}/sync-secrets.js +159 -55
  51. package/dist/bin/commands/sync-secrets.js.map +1 -0
  52. package/dist/bin/commands/validate.d.ts.map +1 -1
  53. package/dist/bin/commands/validate.js +2 -12
  54. package/dist/bin/commands/validate.js.map +1 -1
  55. package/dist/lib/alb-api-gateway.d.ts +7 -1
  56. package/dist/lib/alb-api-gateway.d.ts.map +1 -1
  57. package/dist/lib/alb-api-gateway.js +9 -6
  58. package/dist/lib/alb-api-gateway.js.map +1 -1
  59. package/dist/lib/benchling-webhook-stack.d.ts +13 -12
  60. package/dist/lib/benchling-webhook-stack.d.ts.map +1 -1
  61. package/dist/lib/benchling-webhook-stack.js +43 -30
  62. package/dist/lib/benchling-webhook-stack.js.map +1 -1
  63. package/dist/lib/configuration-saver.d.ts +4 -16
  64. package/dist/lib/configuration-saver.d.ts.map +1 -1
  65. package/dist/lib/configuration-saver.js +14 -54
  66. package/dist/lib/configuration-saver.js.map +1 -1
  67. package/dist/lib/fargate-service.d.ts +11 -21
  68. package/dist/lib/fargate-service.d.ts.map +1 -1
  69. package/dist/lib/fargate-service.js +79 -176
  70. package/dist/lib/fargate-service.js.map +1 -1
  71. package/dist/lib/types/config.d.ts +591 -224
  72. package/dist/lib/types/config.d.ts.map +1 -1
  73. package/dist/lib/types/config.js +134 -3
  74. package/dist/lib/types/config.js.map +1 -1
  75. package/dist/lib/utils/config.d.ts.map +1 -1
  76. package/dist/lib/utils/config.js.map +1 -1
  77. package/dist/lib/xdg-config.d.ts +222 -106
  78. package/dist/lib/xdg-config.d.ts.map +1 -1
  79. package/dist/lib/xdg-config.js +448 -387
  80. package/dist/lib/xdg-config.js.map +1 -1
  81. package/dist/package.json +16 -13
  82. package/dist/scripts/check-logs.d.ts +12 -0
  83. package/dist/scripts/check-logs.d.ts.map +1 -0
  84. package/dist/{bin → scripts}/check-logs.js +65 -15
  85. package/dist/scripts/check-logs.js.map +1 -0
  86. package/dist/scripts/check-webhook-verification.d.ts +3 -0
  87. package/dist/scripts/check-webhook-verification.d.ts.map +1 -0
  88. package/dist/{bin/test-invalid-signature.js → scripts/check-webhook-verification.js} +1 -1
  89. package/dist/scripts/check-webhook-verification.js.map +1 -0
  90. package/dist/scripts/infer-quilt-config.d.ts +23 -26
  91. package/dist/scripts/infer-quilt-config.d.ts.map +1 -1
  92. package/dist/scripts/infer-quilt-config.js +58 -96
  93. package/dist/scripts/infer-quilt-config.js.map +1 -1
  94. package/dist/scripts/send-event.d.ts.map +1 -0
  95. package/dist/scripts/send-event.js.map +1 -0
  96. package/dist/{bin → scripts}/version.d.ts +3 -1
  97. package/dist/scripts/version.d.ts.map +1 -0
  98. package/dist/{bin → scripts}/version.js +95 -9
  99. package/dist/scripts/version.js.map +1 -0
  100. package/package.json +16 -13
  101. package/dist/bin/check-logs.d.ts +0 -7
  102. package/dist/bin/check-logs.d.ts.map +0 -1
  103. package/dist/bin/check-logs.js.map +0 -1
  104. package/dist/bin/config-profiles.d.ts.map +0 -1
  105. package/dist/bin/config-profiles.js.map +0 -1
  106. package/dist/bin/create-secret.d.ts.map +0 -1
  107. package/dist/bin/create-secret.js.map +0 -1
  108. package/dist/bin/dev-deploy.d.ts +0 -20
  109. package/dist/bin/dev-deploy.d.ts.map +0 -1
  110. package/dist/bin/dev-deploy.js +0 -289
  111. package/dist/bin/dev-deploy.js.map +0 -1
  112. package/dist/bin/get-env.d.ts.map +0 -1
  113. package/dist/bin/get-env.js.map +0 -1
  114. package/dist/bin/publish.d.ts.map +0 -1
  115. package/dist/bin/publish.js.map +0 -1
  116. package/dist/bin/release.d.ts +0 -11
  117. package/dist/bin/release.d.ts.map +0 -1
  118. package/dist/bin/release.js +0 -141
  119. package/dist/bin/release.js.map +0 -1
  120. package/dist/bin/send-event.d.ts.map +0 -1
  121. package/dist/bin/send-event.js.map +0 -1
  122. package/dist/bin/test-invalid-signature.d.ts +0 -3
  123. package/dist/bin/test-invalid-signature.d.ts.map +0 -1
  124. package/dist/bin/test-invalid-signature.js.map +0 -1
  125. package/dist/bin/version.d.ts.map +0 -1
  126. package/dist/bin/version.js.map +0 -1
  127. package/dist/scripts/config-health-check.d.ts +0 -84
  128. package/dist/scripts/config-health-check.d.ts.map +0 -1
  129. package/dist/scripts/config-health-check.js +0 -659
  130. package/dist/scripts/config-health-check.js.map +0 -1
  131. package/dist/scripts/install-wizard.d.ts +0 -34
  132. package/dist/scripts/install-wizard.d.ts.map +0 -1
  133. package/dist/scripts/install-wizard.js +0 -719
  134. package/dist/scripts/install-wizard.js.map +0 -1
  135. package/dist/scripts/sync-secrets.d.ts.map +0 -1
  136. package/dist/scripts/sync-secrets.js.map +0 -1
  137. /package/dist/bin/{create-secret.d.ts → commands/create-secret.d.ts} +0 -0
  138. /package/dist/bin/{create-secret.js → commands/create-secret.js} +0 -0
  139. /package/dist/bin/{publish.d.ts → commands/publish.d.ts} +0 -0
  140. /package/dist/{bin → scripts}/send-event.d.ts +0 -0
  141. /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,850 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
46
52
  };
47
53
  Object.defineProperty(exports, "__esModule", { value: true });
48
54
  exports.setupWizardCommand = setupWizardCommand;
55
+ const https = __importStar(require("https"));
56
+ const fs_1 = require("fs");
57
+ const path_1 = require("path");
58
+ const inquirer_1 = __importDefault(require("inquirer"));
49
59
  const chalk_1 = __importDefault(require("chalk"));
50
- const install_wizard_1 = require("../../scripts/install-wizard");
60
+ const boxen_1 = __importDefault(require("boxen"));
61
+ const ora_1 = __importDefault(require("ora"));
62
+ const client_s3_1 = require("@aws-sdk/client-s3");
63
+ const xdg_config_1 = require("../../lib/xdg-config");
64
+ 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
+ }
82
+ // =============================================================================
83
+ // VALIDATION FUNCTIONS (from scripts/config/validator.ts)
84
+ // =============================================================================
51
85
  /**
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
86
+ * Validates Benchling tenant accessibility
87
+ */
88
+ async function validateBenchlingTenant(tenant) {
89
+ const result = {
90
+ isValid: false,
91
+ errors: [],
92
+ warnings: [],
93
+ };
94
+ if (!tenant || tenant.trim().length === 0) {
95
+ result.errors.push("Tenant name cannot be empty");
96
+ return result;
97
+ }
98
+ // Basic format validation
99
+ if (!/^[a-zA-Z0-9-_]+$/.test(tenant)) {
100
+ result.errors.push("Tenant name contains invalid characters (only alphanumeric, dash, underscore allowed)");
101
+ return result;
102
+ }
103
+ // Test tenant URL accessibility
104
+ const tenantUrl = `https://${tenant}.benchling.com`;
105
+ return new Promise((resolve) => {
106
+ https
107
+ .get(tenantUrl, { timeout: 5000 }, (res) => {
108
+ if (res.statusCode === 200 || res.statusCode === 302 || res.statusCode === 301) {
109
+ result.isValid = true;
110
+ console.log(` ✓ Tenant URL accessible: ${tenantUrl}`);
111
+ }
112
+ else {
113
+ if (!result.warnings)
114
+ result.warnings = [];
115
+ result.warnings.push(`Tenant URL returned status ${res.statusCode}`);
116
+ result.isValid = true; // Consider this a warning, not an error
117
+ }
118
+ resolve(result);
119
+ })
120
+ .on("error", (error) => {
121
+ if (!result.warnings)
122
+ result.warnings = [];
123
+ result.warnings.push(`Could not verify tenant URL: ${error.message}`);
124
+ result.isValid = true; // Allow proceeding with warning
125
+ resolve(result);
126
+ });
127
+ });
128
+ }
129
+ /**
130
+ * Validates Benchling OAuth credentials
61
131
  */
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,
132
+ async function validateBenchlingCredentials(tenant, clientId, clientSecret) {
133
+ const result = {
134
+ isValid: false,
135
+ errors: [],
136
+ warnings: [],
137
+ };
138
+ if (!clientId || clientId.trim().length === 0) {
139
+ result.errors.push("Client ID cannot be empty");
140
+ }
141
+ if (!clientSecret || clientSecret.trim().length === 0) {
142
+ result.errors.push("Client secret cannot be empty");
143
+ }
144
+ if (result.errors.length > 0) {
145
+ return result;
146
+ }
147
+ // Test OAuth token endpoint
148
+ const tokenUrl = `https://${tenant}.benchling.com/api/v2/token`;
149
+ const authString = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
150
+ return new Promise((resolve) => {
151
+ const postData = "grant_type=client_credentials";
152
+ const options = {
153
+ method: "POST",
154
+ headers: {
155
+ "Authorization": `Basic ${authString}`,
156
+ "Content-Type": "application/x-www-form-urlencoded",
157
+ "Content-Length": postData.length,
158
+ },
159
+ timeout: 10000,
160
+ };
161
+ const req = https.request(tokenUrl, options, (res) => {
162
+ let data = "";
163
+ res.on("data", (chunk) => {
164
+ data += chunk;
165
+ });
166
+ res.on("end", () => {
167
+ if (res.statusCode === 200) {
168
+ result.isValid = true;
169
+ console.log(" ✓ OAuth credentials validated successfully");
170
+ }
171
+ else {
172
+ result.errors.push(`OAuth validation failed with status ${res.statusCode}: ${data.substring(0, 100)}`);
173
+ }
174
+ resolve(result);
175
+ });
176
+ });
177
+ req.on("error", (error) => {
178
+ if (!result.warnings)
179
+ result.warnings = [];
180
+ result.warnings.push(`Could not validate OAuth credentials: ${error.message}`);
181
+ result.isValid = true; // Allow proceeding with warning
182
+ resolve(result);
183
+ });
184
+ req.write(postData);
185
+ req.end();
74
186
  });
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([
187
+ }
188
+ /**
189
+ * Validates S3 bucket access
190
+ */
191
+ async function validateS3BucketAccess(bucketName, region, awsProfile) {
192
+ const result = {
193
+ isValid: false,
194
+ errors: [],
195
+ warnings: [],
196
+ };
197
+ if (!bucketName || bucketName.trim().length === 0) {
198
+ result.errors.push("Bucket name cannot be empty");
199
+ return result;
200
+ }
201
+ try {
202
+ const clientConfig = { region };
203
+ if (awsProfile) {
204
+ const { fromIni } = await Promise.resolve().then(() => __importStar(require("@aws-sdk/credential-providers")));
205
+ clientConfig.credentials = fromIni({ profile: awsProfile });
206
+ }
207
+ const s3Client = new client_s3_1.S3Client(clientConfig);
208
+ // Test HeadBucket (verify bucket exists and we have access)
209
+ const headCommand = new client_s3_1.HeadBucketCommand({ Bucket: bucketName });
210
+ await s3Client.send(headCommand);
211
+ console.log(` ✓ S3 bucket accessible: ${bucketName}`);
212
+ // Test ListObjects (verify we can list objects)
213
+ const listCommand = new client_s3_1.ListObjectsV2Command({
214
+ Bucket: bucketName,
215
+ MaxKeys: 1,
216
+ });
217
+ await s3Client.send(listCommand);
218
+ console.log(" ✓ S3 bucket list permission confirmed");
219
+ result.isValid = true;
220
+ }
221
+ catch (error) {
222
+ const err = error;
223
+ result.errors.push(`S3 bucket validation failed: ${err.message}`);
224
+ }
225
+ return result;
226
+ }
227
+ /**
228
+ * Validates complete ProfileConfig
229
+ */
230
+ async function validateConfig(config, options = {}) {
231
+ const result = {
232
+ isValid: true,
233
+ errors: [],
234
+ warnings: [],
235
+ };
236
+ if (options.skipValidation) {
237
+ return result;
238
+ }
239
+ // Validate Benchling tenant
240
+ const tenantValidation = await validateBenchlingTenant(config.benchling.tenant);
241
+ if (!tenantValidation.isValid) {
242
+ result.isValid = false;
243
+ result.errors.push(...tenantValidation.errors);
244
+ }
245
+ if (tenantValidation.warnings && tenantValidation.warnings.length > 0) {
246
+ if (!result.warnings)
247
+ result.warnings = [];
248
+ result.warnings.push(...tenantValidation.warnings);
249
+ }
250
+ // Validate OAuth credentials (if secret is provided)
251
+ if (config.benchling.clientSecret) {
252
+ const credValidation = await validateBenchlingCredentials(config.benchling.tenant, config.benchling.clientId, config.benchling.clientSecret);
253
+ if (!credValidation.isValid) {
254
+ result.isValid = false;
255
+ result.errors.push(...credValidation.errors);
256
+ }
257
+ if (credValidation.warnings && credValidation.warnings.length > 0) {
258
+ if (!result.warnings)
259
+ result.warnings = [];
260
+ result.warnings.push(...credValidation.warnings);
261
+ }
262
+ }
263
+ // Validate S3 bucket access
264
+ const bucketValidation = await validateS3BucketAccess(config.packages.bucket, config.deployment.region, options.awsProfile);
265
+ if (!bucketValidation.isValid) {
266
+ result.isValid = false;
267
+ result.errors.push(...bucketValidation.errors);
268
+ }
269
+ if (bucketValidation.warnings && bucketValidation.warnings.length > 0) {
270
+ if (!result.warnings)
271
+ result.warnings = [];
272
+ result.warnings.push(...bucketValidation.warnings);
273
+ }
274
+ return result;
275
+ }
276
+ /**
277
+ * Runs interactive configuration wizard
278
+ */
279
+ async function runConfigWizard(options = {}) {
280
+ const { existingConfig = {}, nonInteractive = false, inheritFrom } = options;
281
+ console.log("╔═══════════════════════════════════════════════════════════╗");
282
+ console.log("║ Benchling Webhook Configuration Wizard ║");
283
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
284
+ if (inheritFrom) {
285
+ console.log(`Creating profile inheriting from: ${inheritFrom}\n`);
286
+ }
287
+ const config = { ...existingConfig };
288
+ // If non-interactive, validate that all required fields are present
289
+ if (nonInteractive) {
290
+ if (!config.benchling?.tenant || !config.benchling?.clientId || !config.benchling?.clientSecret) {
291
+ throw new Error("Non-interactive mode requires benchlingTenant, benchlingClientId, and benchlingClientSecret to be already configured");
292
+ }
293
+ return config;
294
+ }
295
+ // Prompt for Quilt configuration (if not inherited)
296
+ 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",
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,
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,
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([
477
+ {
478
+ type: "confirm",
479
+ name: "ready",
480
+ message: "Have you created and installed the Benchling app?",
481
+ default: true,
482
+ },
483
+ ]);
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
+ }
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");
572
+ }
573
+ }
574
+ }
575
+ }
576
+ config.benchling = {
577
+ tenant: benchlingTenant,
578
+ clientId: benchlingClientId,
579
+ clientSecret: benchlingClientSecret,
580
+ appDefinitionId: benchlingAppDefinitionId,
581
+ };
582
+ if (benchlingTestEntryId && benchlingTestEntryId.length > 0) {
583
+ config.benchling.testEntryId = benchlingTestEntryId;
584
+ }
585
+ // Prompt for package configuration
586
+ console.log("\nPackage Storage Configuration\n");
587
+ const packageAnswers = await inquirer_1.default.prompt([
588
+ {
589
+ type: "input",
590
+ name: "bucket",
591
+ message: "Package S3 Bucket:",
592
+ default: config.packages?.bucket || config.quilt?.bucket,
593
+ validate: (input) => input.trim().length > 0 || "Bucket name is required",
594
+ },
595
+ {
596
+ type: "input",
597
+ name: "prefix",
598
+ message: "Package S3 prefix:",
599
+ default: config.packages?.prefix || "benchling",
600
+ },
601
+ {
602
+ type: "input",
603
+ name: "metadataKey",
604
+ message: "Package metadata key:",
605
+ default: config.packages?.metadataKey || "experiment_id",
606
+ },
607
+ ]);
608
+ config.packages = {
609
+ bucket: packageAnswers.bucket,
610
+ prefix: packageAnswers.prefix,
611
+ metadataKey: packageAnswers.metadataKey,
612
+ };
613
+ // Prompt for deployment configuration
614
+ console.log("\nDeployment Defaults\n");
615
+ const defaultAccount = config.deployment?.account || getAccountFromStackArn(config.quilt?.stackArn);
616
+ 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
+ {
626
+ type: "input",
627
+ name: "region",
628
+ message: "AWS Deployment Region:",
629
+ default: config.deployment?.region || config.quilt?.region || "us-east-1",
630
+ },
631
+ {
632
+ type: "input",
633
+ name: "imageTag",
634
+ message: "Docker image tag:",
635
+ default: config.deployment?.imageTag || "latest",
636
+ },
637
+ ]);
638
+ config.deployment = {
639
+ ...(deploymentAnswers.account ? { account: deploymentAnswers.account } : {}),
640
+ region: deploymentAnswers.region,
641
+ imageTag: deploymentAnswers.imageTag,
642
+ };
643
+ // Optional: Logging configuration
644
+ console.log("\nSecurity and Logging Options\n");
645
+ const optionalAnswers = await inquirer_1.default.prompt([
646
+ {
647
+ type: "list",
648
+ name: "logLevel",
649
+ message: "Log level:",
650
+ choices: ["DEBUG", "INFO", "WARNING", "ERROR"],
651
+ default: config.logging?.level || "INFO",
652
+ },
79
653
  {
80
654
  type: "confirm",
81
- name: "shouldDeploy",
82
- message: "Would you like to deploy to AWS now?",
83
- default: true,
655
+ name: "enableVerification",
656
+ message: "Enable webhook signature verification:",
657
+ default: config.security?.enableVerification !== false,
658
+ },
659
+ {
660
+ type: "input",
661
+ name: "webhookAllowList",
662
+ message: "Webhook IP allowlist (comma-separated, empty for none):",
663
+ default: config.security?.webhookAllowList || "",
84
664
  },
85
665
  ]);
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"));
666
+ config.logging = {
667
+ level: optionalAnswers.logLevel,
668
+ };
669
+ config.security = {
670
+ enableVerification: optionalAnswers.enableVerification,
671
+ webhookAllowList: optionalAnswers.webhookAllowList,
672
+ };
673
+ // Add metadata
674
+ const now = new Date().toISOString();
675
+ config._metadata = {
676
+ version: pkg.version,
677
+ createdAt: config._metadata?.createdAt || now,
678
+ updatedAt: now,
679
+ source: "wizard",
680
+ };
681
+ // Add inheritance marker if specified
682
+ if (inheritFrom) {
683
+ config._inherits = inheritFrom;
684
+ }
685
+ return config;
686
+ }
687
+ /**
688
+ * Main install wizard function
689
+ *
690
+ * Orchestrates the complete configuration workflow:
691
+ * 1. Load existing configuration (if any)
692
+ * 2. Infer Quilt configuration from AWS
693
+ * 3. Run interactive prompts for missing fields
694
+ * 4. Validate configuration
695
+ * 5. Save to XDG config directory
696
+ */
697
+ 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;
699
+ const xdg = new xdg_config_1.XDGConfig();
700
+ console.log("\n╔═══════════════════════════════════════════════════════════╗");
701
+ const headerLine = `║ Benchling Webhook Setup (v${pkg.version})`;
702
+ console.log(`${headerLine.padEnd(59, " ")}║`);
703
+ console.log("╚═══════════════════════════════════════════════════════════╝\n");
704
+ // Step 1: Load existing configuration (if profile exists)
705
+ let existingConfig;
706
+ if (xdg.profileExists(profile)) {
707
+ console.log(`Loading existing configuration for profile: ${profile}\n`);
708
+ try {
709
+ existingConfig = inheritFrom
710
+ ? xdg.readProfileWithInheritance(profile, inheritFrom)
711
+ : xdg.readProfile(profile);
712
+ }
713
+ catch (error) {
714
+ console.warn(`Warning: Could not load existing config: ${error.message}`);
715
+ }
716
+ }
717
+ else if (inheritFrom) {
718
+ console.log(`Creating new profile '${profile}' inheriting from '${inheritFrom}'\n`);
719
+ try {
720
+ existingConfig = xdg.readProfile(inheritFrom);
721
+ }
722
+ catch (error) {
723
+ throw new Error(`Base profile '${inheritFrom}' not found: ${error.message}`);
724
+ }
725
+ }
726
+ // Step 2: Infer Quilt configuration (unless inheriting from another profile)
727
+ let quiltConfig = existingConfig?.quilt || {};
728
+ if (!inheritFrom || !existingConfig?.quilt) {
729
+ console.log("Step 1: Inferring Quilt configuration from AWS...\n");
730
+ try {
731
+ const inferenceResult = await (0, infer_quilt_config_1.inferQuiltConfig)({
732
+ region: awsRegion,
733
+ profile: awsProfile,
734
+ interactive: !nonInteractive,
735
+ });
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
+ }
759
+ console.log("✓ Quilt configuration inferred\n");
760
+ }
761
+ catch (error) {
762
+ console.error(`Failed to infer Quilt configuration: ${error.message}`);
763
+ if (nonInteractive) {
764
+ throw error;
765
+ }
766
+ const { continueManually } = await inquirer_1.default.prompt([
767
+ {
768
+ type: "confirm",
769
+ name: "continueManually",
770
+ message: "Continue and enter Quilt configuration manually?",
771
+ default: true,
772
+ },
773
+ ]);
774
+ if (!continueManually) {
775
+ throw new Error("Setup aborted by user");
776
+ }
777
+ }
778
+ }
779
+ // Merge inferred Quilt config with existing config
780
+ const partialConfig = {
781
+ ...existingConfig,
782
+ quilt: {
783
+ ...existingConfig?.quilt,
784
+ ...quiltConfig,
785
+ },
786
+ };
787
+ // Step 3: Run interactive wizard for remaining configuration
788
+ let config = await runConfigWizard({
789
+ existingConfig: partialConfig,
790
+ nonInteractive,
791
+ inheritFrom,
792
+ });
793
+ // Step 4: Validate configuration
794
+ if (!skipValidation) {
795
+ console.log("\nValidating configuration...\n");
796
+ const validation = await validateConfig(config, {
797
+ skipValidation,
798
+ awsProfile,
799
+ });
800
+ if (!validation.isValid) {
801
+ console.error("\n❌ Configuration validation failed:");
802
+ validation.errors.forEach((err) => console.error(` - ${err}`));
803
+ if (nonInteractive) {
804
+ throw new Error("Configuration validation failed");
805
+ }
806
+ const { proceed } = await inquirer_1.default.prompt([
807
+ {
808
+ type: "confirm",
809
+ name: "proceed",
810
+ message: "Save configuration anyway?",
811
+ default: false,
812
+ },
813
+ ]);
814
+ if (!proceed) {
815
+ throw new Error("Setup aborted by user");
816
+ }
817
+ }
818
+ else {
819
+ console.log("✓ Configuration validated successfully\n");
820
+ }
821
+ if (validation.warnings && validation.warnings.length > 0) {
822
+ console.warn("\n⚠ Warnings:");
823
+ validation.warnings.forEach((warn) => console.warn(` - ${warn}`));
824
+ console.log("");
825
+ }
826
+ }
827
+ // Step 5: Persist configuration locally
828
+ console.log(`Saving configuration to profile: ${profile}...\n`);
829
+ try {
830
+ xdg.writeProfile(profile, config);
831
+ console.log(chalk_1.default.green(`✓ Configuration saved: ~/.config/benchling-webhook/${profile}/config.json\n`));
832
+ }
833
+ catch (error) {
834
+ throw new Error(`Failed to save configuration: ${error.message}`);
835
+ }
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`));
95
841
  }
96
842
  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"));
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
+ }
100
858
  }
101
- console.log(chalk_1.default.dim("\nFor more information: https://github.com/quiltdata/benchling-webhook\n"));
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
+ });
887
+ return config;
888
+ }
889
+ // =============================================================================
890
+ // CLI COMMAND EXPORT
891
+ // =============================================================================
892
+ /**
893
+ * Setup wizard command handler
894
+ *
895
+ * @param options - Wizard options
896
+ * @returns Promise that resolves when wizard completes
897
+ */
898
+ async function setupWizardCommand(options = {}) {
899
+ await runInstallWizard(options);
102
900
  }
103
901
  //# sourceMappingURL=setup-wizard.js.map